diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index 369da734da..d01c26d511 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -259,7 +259,7 @@ jobs: - name: Run clippy shell: bash - run: just clippy + run: just clippy --jobs 2 - name: Check Cargo.lock changed if: ${{ (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} diff --git a/.github/workflows/CI-push.yml b/.github/workflows/CI-push.yml index 7bc5599588..e17ce46d85 100644 --- a/.github/workflows/CI-push.yml +++ b/.github/workflows/CI-push.yml @@ -42,6 +42,8 @@ jobs: runs-on: ${{ matrix.runner }} env: + # Use `sccache` for caching compilation artifacts + RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -75,10 +77,16 @@ jobs: uses: arduino/setup-protoc@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - - uses: Swatinem/rust-cache@v2.7.0 + + - name: Install sccache + uses: taiki-e/install-action@v2.33.28 with: - save-if: ${{ github.event_name == 'push' }} + tool: sccache + checksum: true + + # - uses: Swatinem/rust-cache@v2.7.0 + # with: + # save-if: ${{ github.event_name == 'push' }} - name: Install cargo-hack uses: baptiste0928/cargo-install@v2.2.0 @@ -131,6 +139,8 @@ jobs: runs-on: ${{ matrix.runner }} env: + # Use `sccache` for caching compilation artifacts + RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -160,6 +170,12 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install sccache + uses: taiki-e/install-action@v2.33.28 + with: + tool: sccache + checksum: true + - name: Install cargo-hack uses: baptiste0928/cargo-install@v2.2.0 with: @@ -180,13 +196,13 @@ jobs: # with: # crate: cargo-nextest - - uses: Swatinem/rust-cache@v2.7.0 - with: - save-if: ${{ github.event_name == 'push' }} + # - uses: Swatinem/rust-cache@v2.7.0 + # with: + # save-if: ${{ github.event_name == 'push' }} - name: Run clippy shell: bash - run: just clippy + run: just clippy --jobs 2 - name: Cargo hack if: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index bc83c2388f..4de046499a 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -14,7 +14,7 @@ env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 PAYMENTS_CONNECTORS: "cybersource stripe" - PAYOUTS_CONNECTORS: "adyenplatform wise" + PAYOUTS_CONNECTORS: "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')}} @@ -128,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: "aa328308-b34e-41b7-a590-4fe45cfe7991.json.gpg" + S3_SOURCE_FILE_NAME: "5b3ebdad-1067-421b-83e5-3ceb73c3eeeb.json.gpg" shell: bash run: | mkdir -p ".github/secrets" ".github/test" @@ -149,7 +149,7 @@ jobs: run: | LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV - + - name: Install mold linker if: ${{ runner.os == 'Linux' && env.RUN_TESTS == 'true' }} uses: rui314/setup-mold@v1 @@ -170,13 +170,6 @@ jobs: tool: sccache checksum: true - - name: Install cargo-nextest - if: ${{ env.RUN_TESTS == 'true' }} - uses: taiki-e/install-action@v2.41.10 - with: - tool: cargo-nextest - checksum: true - - name: Install Diesel CLI if: ${{ env.RUN_TESTS == 'true' }} uses: baptiste0928/cargo-install@v3.1.1 @@ -222,6 +215,11 @@ jobs: - name: Setup Local Server if: ${{ env.RUN_TESTS == 'true' }} + env: + ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE }} + ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_PPC_KEY: ${{ secrets.APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY }} + ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE }} + ROUTER__APPLEPAY_DECRYPT_KEYS__APPLE_PAY_MERCHANT_CERT_KEY: ${{ secrets.APPLE_PAY_MERCHANT_CERTIFICATE_KEY }} run: | # Start the server in the background target/debug/router & @@ -261,3 +259,211 @@ jobs: path: | cypress-tests/cypress/reports/ retention-days: 1 + + runner_v2: + name: Run Cypress tests on v2 and generate coverage report + runs-on: hyperswitch-runners + env: + CODECOV_FILE: "lcov.info" + + services: + redis: + image: "public.ecr.aws/docker/library/redis:alpine" + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + postgres: + image: "public.ecr.aws/docker/library/postgres:alpine" + env: + POSTGRES_USER: db_user + POSTGRES_PASSWORD: db_pass + POSTGRES_DB: hyperswitch_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Skip tests for PRs from forks + shell: bash + if: ${{ env.RUN_TESTS == 'false' }} + run: echo 'Skipping tests for PRs from forks' + + - name: Checkout repository + if: ${{ env.RUN_TESTS == 'true' }} + uses: actions/checkout@v4 + + - name: Download Encrypted TOML from S3 and Decrypt + if: ${{ env.RUN_TESTS == 'true' }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} + AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} + 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: "aa328308-b34e-41b7-a590-4fe45cfe7991.json.gpg" + shell: bash + run: | + mkdir -p ".github/secrets" ".github/test" + + aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" ".github/secrets/${DESTINATION_FILE_NAME}" + gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output ".github/test/creds.json" ".github/secrets/${DESTINATION_FILE_NAME}" + + - name: Set paths in env + if: ${{ env.RUN_TESTS == 'true' }} + shell: bash + run: | + echo "CYPRESS_CONNECTOR_AUTH_FILE_PATH=${{ github.workspace }}/.github/test/creds.json" >> $GITHUB_ENV + + - name: Fetch keys + if: ${{ env.RUN_TESTS == 'true' }} + env: + TOML_PATH: "./config/development.toml" + run: | + LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) + echo "CYPRESS_ADMINAPIKEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV + + - name: Install mold linker + if: ${{ runner.os == 'Linux' && env.RUN_TESTS == 'true' }} + uses: rui314/setup-mold@v1 + with: + make-default: true + + - name: Install Rust + if: ${{ env.RUN_TESTS == 'true' }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable 2 weeks ago + components: llvm-tools-preview + + - name: Install sccache + if: ${{ env.RUN_TESTS == 'true' }} + uses: taiki-e/install-action@v2.41.10 + with: + tool: sccache + checksum: true + + - name: Install Diesel CLI + if: ${{ env.RUN_TESTS == 'true' }} + uses: baptiste0928/cargo-install@v3.1.1 + with: + crate: diesel_cli + features: postgres + args: --no-default-features + + - name: Install grcov + if: ${{ env.RUN_TESTS == 'true' }} + uses: taiki-e/install-action@v2.41.10 + with: + tool: grcov + checksum: true + + - name: Install Just + if: ${{ env.RUN_TESTS == 'true' }} + uses: taiki-e/install-action@v2.41.10 + with: + tool: just + checksum: true + + - name: Install Node.js + if: ${{ env.RUN_TESTS == 'true' }} + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install Cypress and dependencies + if: ${{ env.RUN_TESTS == 'true' }} + run: | + npm ci --prefix ./cypress-tests-v2 + + - name: Run database migrations + if: ${{ env.RUN_TESTS == 'true' }} + shell: bash + env: + DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db + run: just migrate_v2 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' }} + env: + RUSTFLAGS: "-Cinstrument-coverage" + run: just build_v2 --jobs 3 + + - name: Setup Local Server + if: ${{ env.RUN_TESTS == 'true' }} + env: + LLVM_PROFILE_FILE: "coverage.profraw" + run: | + # Start the server in the background + target/debug/router & + + SERVER_PID=$! + echo "PID=${SERVER_PID}" >> $GITHUB_ENV + + # Wait for the server to start in port 8080 + COUNT=0 + while ! nc -z localhost 8080; do + if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) + echo "Server did not start within a reasonable time. Exiting." + kill ${SERVER_PID} + exit 1 + else + COUNT=$((COUNT+1)) + sleep 10 + fi + done + + - name: Run Cypress tests + if: ${{ env.RUN_TESTS == 'true' }} + env: + CYPRESS_BASEURL: "http://localhost:8080" + ROUTER__SERVER__WORKERS: 4 + shell: bash -leuo pipefail {0} + continue-on-error: true + # We aren't specifying `command` and `jobs` arguments currently + run: scripts/execute_cypress.sh "" "" "cypress-tests-v2" + + - name: Stop running server + if: ${{ env.RUN_TESTS == 'true' }} && always() + run: | + kill "${{ env.PID }}" + + - name: Upload Cypress test results + if: env.RUN_TESTS == 'true' && failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-v2-test-results + path: | + cypress-tests-v2/cypress/reports/ + retention-days: 1 + + # Notes: + # - The `router` process must be killed (using SIGINT/SIGTERM) to generate the `coverage.profraw` file, otherwise the coverage will only be generated for the buildscripts + # - Trying to generate branch coverage using "-Z coverage-options=branch" currently fails. Both grcov and cargo-llvm-cov crash when trying + # to process the generated `.profraw` files. + # - --keep-only argument is used to exclude external crates in generated lcov.info file (~500MiB -> ~70MiB) + - name: Process coverage report + if: ${{ env.RUN_TESTS == 'true' && github.event_name != 'merge_group' }} + run: grcov . --source-dir . --output-types lcov --output-path ${{ env.CODECOV_FILE }} --binary-path ./target/debug --keep-only "crates/*" + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + if: ${{ env.RUN_TESTS == 'true' && github.event_name != 'merge_group'}} + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ env.CODECOV_FILE }} + disable_search: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index dcbeddf7ad..d53145b33e 100644 --- a/.gitignore +++ b/.gitignore @@ -241,6 +241,7 @@ $RECYCLE.BIN/ # hyperswitch Project specific excludes # code coverage report *.profraw +lcov.info html/ coverage.json # other diff --git a/CHANGELOG.md b/CHANGELOG.md index ee0640a564..f8594f6be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,197 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.01.23.0 + +### Features + +- **connector:** [ADYEN ] Consume transaction id for PaymentsPreProcessing error ([#7061](https://github.com/juspay/hyperswitch/pull/7061)) ([`199d176`](https://github.com/juspay/hyperswitch/commit/199d1764488f234accab3bfecef9645ee9486057)) + +### Refactors + +- [CYBERSOURCE, BANKOFAMERICA, WELLSFARGO] Move code to crate hyperswitch_connectors ([#6908](https://github.com/juspay/hyperswitch/pull/6908)) ([`be01896`](https://github.com/juspay/hyperswitch/commit/be018963c6696c3f494bdd45825ebc61ba1bbc82)) + +### Miscellaneous Tasks + +- Enable 128-column-tables feature for diesel crate ([#6857](https://github.com/juspay/hyperswitch/pull/6857)) ([`eaf450b`](https://github.com/juspay/hyperswitch/commit/eaf450b91109c21e1091f7936cab009e8e6e2abb)) + +**Full Changelog:** [`2025.01.22.0...2025.01.23.0`](https://github.com/juspay/hyperswitch/compare/2025.01.22.0...2025.01.23.0) + +- - - + +## 2025.01.22.0 + +### Features + +- **connectors:** Fiuu,novalnet,worldpay - extend NTI flows ([#6946](https://github.com/juspay/hyperswitch/pull/6946)) ([`d6b0660`](https://github.com/juspay/hyperswitch/commit/d6b0660569eb8bbbc6557aa6ed29184fe51ab209)) +- **email:** Add mailhog by default in docker-compose for local smtp server ([#6869](https://github.com/juspay/hyperswitch/pull/6869)) ([`100a178`](https://github.com/juspay/hyperswitch/commit/100a1783ac79b1f0888de786c5d12ce813126c21)) +- **router:** Add payment method-specific features to connector feature list ([#6963](https://github.com/juspay/hyperswitch/pull/6963)) ([`e35f707`](https://github.com/juspay/hyperswitch/commit/e35f7079e3fc9ada76d0602739053bdd5d595008)) +- **routing:** Integrate global success rates ([#6950](https://github.com/juspay/hyperswitch/pull/6950)) ([`39d2d6c`](https://github.com/juspay/hyperswitch/commit/39d2d6c43800f609070b61a6148ddef7e40001bc)) + +### Bug Fixes + +- **cypress:** Address cybersource redirection inconsistency ([#7057](https://github.com/juspay/hyperswitch/pull/7057)) ([`90c932a`](https://github.com/juspay/hyperswitch/commit/90c932a6d798453f7e828c55a7668c5c64c933a5)) + +### Refactors + +- Customer email and browser Information ([#7034](https://github.com/juspay/hyperswitch/pull/7034)) ([`d35a922`](https://github.com/juspay/hyperswitch/commit/d35a9222815e9259a6097eabd41cd458650cb62e)) +- Check allowed payment method types in enabled options ([#7019](https://github.com/juspay/hyperswitch/pull/7019)) ([`0eca55f`](https://github.com/juspay/hyperswitch/commit/0eca55f75392f8091a1cf8f378e8cbee9afd3eac)) + +**Full Changelog:** [`2025.01.21.0...2025.01.22.0`](https://github.com/juspay/hyperswitch/compare/2025.01.21.0...2025.01.22.0) + +- - - + +## 2025.01.21.0 + +### Refactors + +- **payment-link:** Use shouldRemoveBeforeUnloadEvents flag for handling removal of beforeunload events through SDK ([#7072](https://github.com/juspay/hyperswitch/pull/7072)) ([`776ed9a`](https://github.com/juspay/hyperswitch/commit/776ed9a2eb0e5ad8125112fe01bb3ea4b34195bc)) +- **router:** Refactor ctp flow to fetch mca_id and get the connector creds instead of connector_name ([#6859](https://github.com/juspay/hyperswitch/pull/6859)) ([`e9fcfc4`](https://github.com/juspay/hyperswitch/commit/e9fcfc4560321ea494afc9a01c06613240592371)) + +**Full Changelog:** [`2025.01.20.0...2025.01.21.0`](https://github.com/juspay/hyperswitch/compare/2025.01.20.0...2025.01.21.0) + +- - - + +## 2025.01.20.0 + +### Bug Fixes + +- **connector:** [NETCETERA] add accept-language in browser_information for netcetera authentication request ([#7059](https://github.com/juspay/hyperswitch/pull/7059)) ([`aa8e2e7`](https://github.com/juspay/hyperswitch/commit/aa8e2e73ebda3d7764c03067fe5bc9b086683dc7)) +- **relay:** Populate connector metadata in the refunds flow ([#7045](https://github.com/juspay/hyperswitch/pull/7045)) ([`e38078f`](https://github.com/juspay/hyperswitch/commit/e38078f152a4326e6ede3d91391a059af399c379)) + +### Miscellaneous Tasks + +- Update creds ([#7054](https://github.com/juspay/hyperswitch/pull/7054)) ([`37f10fb`](https://github.com/juspay/hyperswitch/commit/37f10fb5b4363244bbe133407e632cece1d9a1c6)) +- Update readme with juspay's vision, product offering, architecture diagram, setup steps and output ([#7024](https://github.com/juspay/hyperswitch/pull/7024)) ([`d01172a`](https://github.com/juspay/hyperswitch/commit/d01172a613b8e74564eef792b8a6915c647854fc)) + +**Full Changelog:** [`2025.01.17.0...2025.01.20.0`](https://github.com/juspay/hyperswitch/compare/2025.01.17.0...2025.01.20.0) + +- - - + +## 2025.01.17.0 + +### Refactors + +- **dynamic_routing:** Add info logs to log the grpc request and response ([#6962](https://github.com/juspay/hyperswitch/pull/6962)) ([`7290484`](https://github.com/juspay/hyperswitch/commit/72904842ed0092e16e2d9980e1d4968df03cafb6)) +- **redis_interface:** Make the redis command for using scripts to write into redis Generic ([#6965](https://github.com/juspay/hyperswitch/pull/6965)) ([`3434651`](https://github.com/juspay/hyperswitch/commit/343465165be0a573ec78a99be93386102a2a6615)) + +**Full Changelog:** [`2025.01.16.0...2025.01.17.0`](https://github.com/juspay/hyperswitch/compare/2025.01.16.0...2025.01.17.0) + +- - - + +## 2025.01.16.0 + +### Features + +- **connector:** [Xendit] ADD Cards & Mandates Flow ([#6966](https://github.com/juspay/hyperswitch/pull/6966)) ([`bbf8844`](https://github.com/juspay/hyperswitch/commit/bbf884460c010e6ebc5f93f6fe6ff079e2463d90)) +- **core:** Diesel models, domain models and db interface changes for callback_mapper table ([#6571](https://github.com/juspay/hyperswitch/pull/6571)) ([`043cf8e`](https://github.com/juspay/hyperswitch/commit/043cf8e0c14e1818ec8e931140f1694d10b7b837)) + +### Refactors + +- **dynamic_routing:** Perform db operations for dynamic_routing_stats table only when payments are in terminal state ([#6900](https://github.com/juspay/hyperswitch/pull/6900)) ([`1ec91e5`](https://github.com/juspay/hyperswitch/commit/1ec91e54e2420d4bed10e82ba1e3da5a1f29251a)) +- **proxy:** Specify hosts for proxy exclusion instead of complete URLs ([#6957](https://github.com/juspay/hyperswitch/pull/6957)) ([`bd1f077`](https://github.com/juspay/hyperswitch/commit/bd1f07705747ebe915ddf88cf860f2ac7c65e9b5)) + +### Miscellaneous Tasks + +- Address Rust 1.84.0 clippy lints ([#7021](https://github.com/juspay/hyperswitch/pull/7021)) ([`4664d4b`](https://github.com/juspay/hyperswitch/commit/4664d4bc4b7e685ab6dfb9176a3309026d3032e9)) + +**Full Changelog:** [`2025.01.14.0...2025.01.16.0`](https://github.com/juspay/hyperswitch/compare/2025.01.14.0...2025.01.16.0) + +- - - + +## 2025.01.14.0 + +### Features + +- **connector:** [Deutschebank] Implement Card 3ds ([#6844](https://github.com/juspay/hyperswitch/pull/6844)) ([`ac75335`](https://github.com/juspay/hyperswitch/commit/ac753352769a67003eedd183e957bee6eb83b103)) +- **payment_methods_v2:** Add payment methods list endpoint ([#6938](https://github.com/juspay/hyperswitch/pull/6938)) ([`6a1f5a8`](https://github.com/juspay/hyperswitch/commit/6a1f5a88750f0683d5e95814c349244ae9c483b0)) + +### Refactors + +- **cypress:** Verify payment status after payment redirection ([#6187](https://github.com/juspay/hyperswitch/pull/6187)) ([`1d99305`](https://github.com/juspay/hyperswitch/commit/1d993055d221eba72c81e3ba0c0b0e6a7e1313a0)) + +**Full Changelog:** [`2025.01.13.0...2025.01.14.0`](https://github.com/juspay/hyperswitch/compare/2025.01.13.0...2025.01.14.0) + +- - - + +## 2025.01.13.0 + +### Features + +- **connector:** [Novalnet] Add zero auth mandate ([#6631](https://github.com/juspay/hyperswitch/pull/6631)) ([`7b306a9`](https://github.com/juspay/hyperswitch/commit/7b306a9015a55b573731414c210d4c684c802f7a)) +- **router:** Add support for relay refund incoming webhooks ([#6974](https://github.com/juspay/hyperswitch/pull/6974)) ([`d850f17`](https://github.com/juspay/hyperswitch/commit/d850f17b87e4eedc66836925136ffbd513d09124)) + +**Full Changelog:** [`2025.01.10.0...2025.01.13.0`](https://github.com/juspay/hyperswitch/compare/2025.01.10.0...2025.01.13.0) + +- - - + +## 2025.01.10.0 + +### Testing + +- **cypress:** Add test for In Memory Cache ([#6961](https://github.com/juspay/hyperswitch/pull/6961)) ([`d8d8c40`](https://github.com/juspay/hyperswitch/commit/d8d8c400bbda49b9a0cd5edbe37e929ae6d38eb4)) + +**Full Changelog:** [`2025.01.09.1...2025.01.10.0`](https://github.com/juspay/hyperswitch/compare/2025.01.09.1...2025.01.10.0) + +- - - + +## 2025.01.09.1 + +### Bug Fixes + +- **dummyconnector:** Add tenant id in dummyconnector requests ([#7008](https://github.com/juspay/hyperswitch/pull/7008)) ([`9c983b6`](https://github.com/juspay/hyperswitch/commit/9c983b68bd834e33c5c57d1d050aa5d41cb10f56)) + +**Full Changelog:** [`2025.01.09.0...2025.01.09.1`](https://github.com/juspay/hyperswitch/compare/2025.01.09.0...2025.01.09.1) + +- - - + +## 2025.01.09.0 + +### Features + +- **users:** Handle edge features for users in tenancy ([#6990](https://github.com/juspay/hyperswitch/pull/6990)) ([`d04e840`](https://github.com/juspay/hyperswitch/commit/d04e840c958595d86590149d92b03cbd61fd69ed)) + +### Bug Fixes + +- **cypress:** Backup and restore sessions when using user apis ([#6978](https://github.com/juspay/hyperswitch/pull/6978)) ([`0b54b37`](https://github.com/juspay/hyperswitch/commit/0b54b375ef42bc46830871db6d0f7b68e386c3f5)) + +### Miscellaneous Tasks + +- **dynamic-fields:** [Worldpay] update dynamic fields for payments ([#7002](https://github.com/juspay/hyperswitch/pull/7002)) ([`b46a921`](https://github.com/juspay/hyperswitch/commit/b46a921ccb05dc194253659c12991d9df7abe71e)) + +**Full Changelog:** [`2025.01.08.0...2025.01.09.0`](https://github.com/juspay/hyperswitch/compare/2025.01.08.0...2025.01.09.0) + +- - - + +## 2025.01.08.0 + +### Features + +- **connector:** [Fiuu] Consume transaction id for error cases for Fiuu ([#6998](https://github.com/juspay/hyperswitch/pull/6998)) ([`6b1e5b0`](https://github.com/juspay/hyperswitch/commit/6b1e5b0aec190b9563df83703efee9cbeaee59fd)) +- **core:** Add columns unified error code and error message in refund table ([#6933](https://github.com/juspay/hyperswitch/pull/6933)) ([`c4d36b5`](https://github.com/juspay/hyperswitch/commit/c4d36b506e159f39acff17e13f72b5c53edec184)) + +### Bug Fixes + +- Consider status of payment method before filtering wallets in list pm ([#7004](https://github.com/juspay/hyperswitch/pull/7004)) ([`d2212cb`](https://github.com/juspay/hyperswitch/commit/d2212cb7eafa37c00ce3a8897a6ae4f1266f01cf)) + +### Documentation + +- **cypress:** Update cypress documentation ([#6956](https://github.com/juspay/hyperswitch/pull/6956)) ([`099bd99`](https://github.com/juspay/hyperswitch/commit/099bd995851a3aa9688f5e160a744c6924f8ec7a)) + +**Full Changelog:** [`2025.01.07.0...2025.01.08.0`](https://github.com/juspay/hyperswitch/compare/2025.01.07.0...2025.01.08.0) + +- - - + +## 2025.01.07.0 + +### Miscellaneous Tasks + +- **keymanager:** Add tenant-id to keymanager requests ([#6968](https://github.com/juspay/hyperswitch/pull/6968)) ([`7901302`](https://github.com/juspay/hyperswitch/commit/79013024ff371efc6062310564b8b56e9bb22701)) + +**Full Changelog:** [`2025.01.06.0...2025.01.07.0`](https://github.com/juspay/hyperswitch/compare/2025.01.06.0...2025.01.07.0) + +- - - + ## 2025.01.06.0 ### Miscellaneous Tasks diff --git a/Cargo.lock b/Cargo.lock index e34c80bd92..7583fb2eee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4003,6 +4003,7 @@ dependencies = [ "hyperswitch_domain_models", "hyperswitch_interfaces", "image", + "josekit", "lazy_static", "masking", "mime", @@ -9266,9 +9267,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if 1.0.0", "once_cell", @@ -9277,13 +9278,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.77", @@ -9304,9 +9304,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9314,9 +9314,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -9327,9 +9327,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" diff --git a/README.md b/README.md index 10fdb6afc7..5b5518d31f 100644 --- a/README.md +++ b/README.md @@ -3,22 +3,11 @@ Hyperswitch-Logo

-

The open-source payments switch

+

Open-Source Payments Orchestration

-The single API to access payment ecosystems across 130+ countries
- -

- Try a Payment • - Quick Setup • - Local Setup Guide (Hyperswitch App Server) • - API Docs -
- Community and Contributions • - Bugs and feature requests • - Versioning • - Copyright and License -

+Single API to access the payments ecosystem and its features +

@@ -30,6 +19,10 @@ The single API to access payment ecosystems across 130+ countries + +

@@ -45,28 +38,46 @@ The single API to access payment ecosystems across 130+ countries


-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. +## Table of Contents -Here are the components of Hyperswitch that deliver the whole solution: +1. [Introduction](#introduction) +2. [Architectural Overview](#architectural-overview) +3. [Try Hyperswitch](#try-hyperswitch) +4. [Support, Feature requests & Bugs](#support-feature-requests) +5. [Our Vision](#our-vision) +6. [Versioning](#versioning) +7. [Copyright and License](#copyright-and-license) -* [Hyperswitch Backend](https://github.com/juspay/hyperswitch): Powering Payment Processing + +

Introduction

+
+Juspay, founded in 2012, is a global leader in payment orchestration and checkout solutions, trusted by 400+ leading enterprises and brands worldwide. Hyperswitch is Juspay's new generation of composable, commercial open-source payments platform for merchant and brands. It is an enterprise-grade, transparent and modular payments platform designed to provide digital businesses access to the best payments infrastructure. -* [SDK (Frontend)](https://github.com/juspay/hyperswitch-web): Simplifying Integration and Powering the UI +Here are the key components of Hyperswitch that deliver the whole solution: -* [Control Centre](https://github.com/juspay/hyperswitch-control-center): Managing Operations with Ease +* [Hyperswitch Backend](https://github.com/juspay/hyperswitch): Hyperswitch backend enables seamless payment processing with comprehensive support for various payment flows - authorization, authentication, void and capture workflows along with robust management of post-payment processes like refunds and chargeback handling. Additionally, Hyperswitch supports non-payment use cases by enabling connections with external FRM or authentication providers as part of the payment flow. The backend optimizes payment routing with customizable workflows, including success rate-based routing, rule-based routing, volume distribution, fallback handling, and intelligent retry mechanisms for failed payments based on specific error codes. -Jump in and contribute to these repositories to help improve and expand Hyperswitch! +* [SDK (Frontend)](https://github.com/juspay/hyperswitch-web): The SDK, available for web, [Android, and iOS](https://github.com/juspay/hyperswitch-client-core), unifies the payment experience across various methods such as cards, wallets, BNPL, bank transfers, and more, while supporting the diverse payment flows of underlying PSPs. When paired with the locker, it surfaces the user's saved payment methods. - +* [Control Center](https://github.com/juspay/hyperswitch-control-center): The Control Center enables users to manage the entire payments stack without any coding. It allows the creation of workflows for routing, payment retries, and defining conditions to invoke 3DS, fraud risk management (FRM), and surcharge modules. The Control Center provides access to transaction, refund, and chargeback operations across all integrated PSPs, transaction-level logs for initial debugging, and detailed analytics and insights into payment performance. + +Read more at [Hyperswitch docs](https://docs.hyperswitch.io/). + + +

Architectural Overview

+
+ + + - -

⚡️ Quick Setup

+
+

Try Hyperswitch

-### Docker Compose +### 1. Local Setup -You can run Hyperswitch on your system using Docker Compose after cloning this repository: +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 @@ -74,23 +85,49 @@ cd hyperswitch docker compose up -d ``` -This will start the app server, web client/SDK 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]. +Check out the [local setup guide][local-setup-guide] for a more details on setting up the entire stack or component wise. This takes 15-mins and gives the following output +```shell +[+] Running 2/2 +✔ hyperswitch-control-center Pulled 2.9s +✔ hyperswitch-server Pulled 3.0s +[+] Running 6/0 + +✔ Container hyperswitch-pg-1 Created 0.0s +✔ Container hyperswitch-redis-standalone-1 Created 0.0s +✔ Container hyperswitch-migration_runner-1 Created 0.0s +✔ Container hyperswitch-hyperswitch-server-1 Created 0.0s +✔ Container hyperswitch-hyperswitch-web-1 Created 0.0s +✔ Container hyperswitch-hyperswitch-control-center-1 Created 0.0s + +Attaching to hyperswitch-control-center-1, hyperswitch-server-1, hyperswitch-web-1, migration_runner-1, pg-1, redis-standalone-1 +``` -### One-click deployment on AWS cloud +### 2. Deployment on cloud -The fastest and easiest way to try Hyperswitch is via our CDK scripts +The fastest and easiest way to try Hyperswitch on AWS is via our CDK scripts 1. Click on the following button for a quick standalone deployment on AWS, suitable for prototyping. No code or setup is required in your system and the deployment is covered within the AWS free-tier setup. - + 2. Sign-in to your AWS console. -3. Follow the instructions provided on the console to successfully deploy Hyperswitch +3. Follow the instructions provided on the console to successfully deploy Hyperswitch. This takes 30-45mins and gives the following output + +| Service| Host| +|----------------------------------------------|----------------------------------------------| +| App server running on | `http://hyperswitch-.elb.amazonaws.com` | +| HyperloaderJS Hosted at | `http:///0.103.1/v0/HyperLoader.js` | +| Control center server running on | `http://hyperswitch-control-center-.elb.amazonaws.com`, Login with Email: `test@gmail.com` | +| Hyperswitch Demo Store running on | `http://hyperswitch-sdk-demo-.elb.amazonaws.com` | +| Logs server running on | `http://hyperswitch-logs-.elb.amazonaws.com`, Login with username: `admin`, password: `admin` | + +We support deployment on GCP and Azure via Helm charts which takes 30-45mins. You can read more at [Hyperswitch docs](https://docs.hyperswitch.io/hyperswitch-open-source/deploy-on-kubernetes-using-helm). + +### 3. Hosted Sandbox + +You can experience the product by signing up for our [hosted sandbox](https://app.hyperswitch.io/). The signup process accepts any email ID and provides access to the entire Control Center. You can set up connectors, define workflows for routing and retries, and even try payments from the dashboard. [docs-link-for-enterprise]: https://docs.hyperswitch.io/hyperswitch-cloud/quickstart [docs-link-for-developers]: https://docs.hyperswitch.io/hyperswitch-open-source/overview @@ -101,43 +138,31 @@ The fastest and easiest way to try Hyperswitch is via our CDK scripts [local-setup-guide]: /docs/try_local_system.md [docker-compose-scheduler-monitoring]: /docs/try_local_system.md#running-additional-services - -

⚡️ Try a Payment

-
- -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! - -

✅ Community & Contributions

+
+

Support, Feature requests & Bugs

-The community and core team are available in [GitHub Discussions](https://github.com/juspay/hyperswitch/discussions), where you can ask for support, discuss roadmap, and share ideas. +For any support, join the conversation in [Slack](https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2jqxmpsbm-WXUENx022HjNEy~Ark7Orw) -Our [Contribution Guide](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md) describes how to contribute to the codebase and Docs. +For new product features, enhancements, roadmap discussions, or to share queries and ideas, visit our [GitHub Discussions](https://github.com/juspay/hyperswitch/discussions) -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) +For reporting a bug, please read the issue guidelines and search for [existing and closed issues]. If your problem or idea is not addressed yet, please [open a new issue]. +[existing and closed issues]: https://github.com/juspay/hyperswitch/issues +[open a new issue]: https://github.com/juspay/hyperswitch/issues/new/choose - -

💪 Join us in building Hyperswitch

+
+

Our Vision

-### 🤝 Our Belief +> Linux for Payments -> Payments should be open, fast, reliable and affordable to serve -> the billions of people at scale. +Payments are evolving rapidly worldwide, with hundreds of processors, fraud detection systems, authentication modules, and new payment methods and flows emerging. Businesses building or managing their own payment stacks often face similar challenges, struggle with comparable issues, and find it hard to innovate at the desired pace. -Globally payment diversity has been growing at a rapid pace. -There are hundreds of payment processors and new payment methods like BNPL, -RTP etc. -Businesses need to embrace this diversity to increase conversion, reduce cost -and improve control. -But integrating and maintaining multiple processors needs a lot of dev effort. -Why should devs across companies repeat the same work? -Why can't it be unified and reused? Hence, Hyperswitch was born to create that -reusable core and let companies build and customise it as per their specific requirements. +Hyperswitch serves as a well-architected designed reference platform, built on best-in-class design principles, empowering businesses to own and customize their payment stack. It provides a reusable core payments stack that can be tailored to specific requirements while relying on the Hyperswitch team for enhancements, support, and continuous innovation. -### ✨ Our Values +### Our Values 1. Embrace Payments Diversity: It will drive innovation in the ecosystem in multiple ways. @@ -150,33 +175,24 @@ reusable core and let companies build and customise it as per their specific req This project is being created and maintained by [Juspay](https://juspay.io) - -

🐞 Bugs and feature requests

-
- -Please read the issue guidelines and search for [existing and closed issues]. -If your problem or idea is not addressed yet, please [open a new issue]. - -[existing and closed issues]: https://github.com/juspay/hyperswitch/issues -[open a new issue]: https://github.com/juspay/hyperswitch/issues/new/choose - - -

🔖 Versioning

+
+

Versioning

Check the [CHANGELOG.md](./CHANGELOG.md) file for details. - - + + This product is licensed under the [Apache 2.0 License](LICENSE). - -

✨ Thanks to all contributors

+ +
+

Team behind Hyperswitch

-Thank you for your support in hyperswitch's growth. Keep up the great work! 🥂 +The core team of 150+ engineers building Hyperswitch. Keep up the great work! 🥂 Contributors diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--payment-methods-list.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--payment-methods-list.mdx new file mode 100644 index 0000000000..13eb3b58aa --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--payment-methods-list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-methods/{id}/list-enabled-payment-methods +--- diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 080adae5ad..d47cf7aee6 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -49,13 +49,12 @@ "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--payment-methods-list", "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" + "api-reference/payment-methods/payment-method--update", + "api-reference/payment-methods/payment-method--retrieve", + "api-reference/payment-methods/payment-method--delete" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index f1caede89c..892931256b 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -2190,62 +2190,19 @@ ] } }, - "/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" - } - } - }, - "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": { + "/v2/payment-methods": { + "post": { "tags": [ "Payment Methods" ], - "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", + "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/PaymentMethodListRequest" + "$ref": "#/components/schemas/PaymentMethodCreate" } } }, @@ -2253,20 +2210,17 @@ }, "responses": { "200": { - "description": "Payment Methods retrieved", + "description": "Payment Method Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + "$ref": "#/components/schemas/PaymentMethodResponse" } } } }, "400": { "description": "Invalid Data" - }, - "404": { - "description": "Payment Methods does not exist in records" } }, "security": [ @@ -2276,19 +2230,19 @@ ] } }, - "/v2/payment-methods": { + "/v2/payment-methods/create-intent": { "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", + "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/PaymentMethodCreate" + "$ref": "#/components/schemas/PaymentMethodIntentCreate" } } }, @@ -2296,7 +2250,7 @@ }, "responses": { "200": { - "description": "Payment Method Created", + "description": "Payment Method Intent Created", "content": { "application/json": { "schema": { @@ -2316,42 +2270,56 @@ ] } }, - "/v2/payment-methods/create-intent": { - "post": { + "/v2/payment-methods/{id}/list-enabled-payment-methods": { + "get": { "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" - } + "summary": "Payment Methods - Payment Methods List", + "description": "List the payment methods eligible for a payment method.", + "operationId": "List Payment methods for a Payment Method Intent", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The global payment method id", + "required": true, + "schema": { + "type": "string" } }, - "required": true - }, + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID associated to the payment method intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Profile-Id": "pro_abcdefghijklmnop" + } + } + ], "responses": { "200": { - "description": "Payment Method Intent Created", + "description": "Get the payment methods", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PaymentMethodResponse" + "$ref": "#/components/schemas/PaymentMethodListResponseForPayments" } } } }, - "400": { - "description": "Invalid Data" + "404": { + "description": "No payment method found with the given id" } }, "security": [ { - "api_key": [] + "api_key": [], + "ephemeral_key": [] } ] } @@ -6425,6 +6393,29 @@ } } }, + "CardSpecificFeatures": { + "type": "object", + "required": [ + "three_ds", + "non_three_ds", + "supported_card_networks" + ], + "properties": { + "three_ds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "non_three_ds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "supported_card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetwork" + }, + "description": "List of supported card networks" + } + } + }, "CardToken": { "type": "object", "required": [ @@ -6612,7 +6603,6 @@ }, "Connector": { "type": "string", - "description": "A connector is an integration to fulfill payments", "enum": [ "adyenplatform", "phonypay", @@ -6696,6 +6686,7 @@ "signifyd", "plaid", "riskified", + "xendit", "zen", "zsl" ] @@ -7461,39 +7452,6 @@ }, "additionalProperties": false }, - "CustomerDefaultPaymentMethodResponse": { - "type": "object", - "required": [ - "customer_id", - "payment_method" - ], - "properties": { - "default_payment_method_id": { - "type": "string", - "description": "The unique identifier of the Payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg", - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The unique identifier of the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "maxLength": 64, - "minLength": 1 - }, - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - } - } - }, "CustomerDeleteResponse": { "type": "object", "required": [ @@ -7994,24 +7952,6 @@ "frictionless" ] }, - "DefaultPaymentMethod": { - "type": "object", - "required": [ - "customer_id", - "payment_method_id" - ], - "properties": { - "customer_id": { - "type": "string", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "maxLength": 64, - "minLength": 1 - }, - "payment_method_id": { - "type": "string" - } - } - }, "DeviceChannel": { "type": "string", "description": "Device Channel indicating whether request is coming from App or Browser", @@ -13896,13 +13836,13 @@ "PaymentMethodDeleteResponse": { "type": "object", "required": [ - "payment_method_id" + "id" ], "properties": { - "payment_method_id": { + "id": { "type": "string", "description": "The unique identifier of the Payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg" + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8" } } }, @@ -14079,67 +14019,15 @@ "PaymentMethodListResponse": { "type": "object", "required": [ - "currency", - "payment_methods", - "mandate_payment", - "show_surcharge_breakup_screen", - "request_external_three_ds_authentication", - "is_tax_calculation_enabled" + "payment_methods_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": { + "payment_methods_enabled": { "type": "array", "items": { - "$ref": "#/components/schemas/ResponsePaymentMethodsEnabled" + "$ref": "#/components/schemas/ResponsePaymentMethodTypes" }, - "description": "Information about the payment method" - }, - "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" + "description": "The list of payment methods that are enabled for the business profile" } } }, @@ -14152,7 +14040,7 @@ "payment_methods_enabled": { "type": "array", "items": { - "$ref": "#/components/schemas/ResponsePaymentMethodTypes" + "$ref": "#/components/schemas/ResponsePaymentMethodTypesForPayments" }, "description": "The list of payment methods that are enabled for the business profile" }, @@ -14169,13 +14057,18 @@ "PaymentMethodResponse": { "type": "object", "required": [ + "id", "merchant_id", "customer_id", - "payment_method_id", "payment_method_type", "recurring_enabled" ], "properties": { + "id": { + "type": "string", + "description": "The unique identifier of the Payment method", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8" + }, "merchant_id": { "type": "string", "description": "Unique identifier for a merchant", @@ -14188,11 +14081,6 @@ "maxLength": 64, "minLength": 32 }, - "payment_method_id": { - "type": "string", - "description": "The unique identifier of the Payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg" - }, "payment_method_type": { "$ref": "#/components/schemas/PaymentMethod" }, @@ -14252,6 +14140,13 @@ } ] }, + "PaymentMethodSpecificFeatures": { + "oneOf": [ + { + "$ref": "#/components/schemas/CardSpecificFeatures" + } + ] + }, "PaymentMethodStatus": { "type": "string", "description": "Payment Method Status", @@ -14266,6 +14161,7 @@ "oneOf": [ { "type": "object", + "title": "card", "required": [ "card_networks" ], @@ -14280,6 +14176,7 @@ }, { "type": "object", + "title": "bank", "required": [ "bank_names" ], @@ -19194,21 +19091,56 @@ "type": "object", "required": [ "payment_method_type", - "payment_method_subtype" + "payment_method_subtype", + "required_fields" ], "properties": { "payment_method_type": { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/PaymentMethod" }, "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": { + "type": "array", + "items": { "$ref": "#/components/schemas/RequiredFieldInfo" }, + "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." + } + } + } + ] + }, + "ResponsePaymentMethodTypesForPayments": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodSubtypeSpecificData" + } + ], + "nullable": true + }, + { + "type": "object", + "required": [ + "payment_method_type", + "payment_method_subtype" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "required_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/RequiredFieldInfo" + } + ], "nullable": true }, "surcharge_details": { @@ -19441,7 +19373,7 @@ }, "RoutableConnectors": { "type": "string", - "description": "Connectors eligible for payments routing", + "description": "RoutableConnectors are the subset of Connectors that are eligible for payments routing", "enum": [ "adyenplatform", "phonypay", @@ -20678,50 +20610,62 @@ "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, + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodSpecificFeatures" + } + ], "nullable": true }, - "supported_currencies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Currency" - }, - "uniqueItems": true, - "nullable": true + { + "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", diff --git a/api-reference/mint.json b/api-reference/mint.json index 5e9c9c6d36..1130da2ffb 100644 --- a/api-reference/mint.json +++ b/api-reference/mint.json @@ -253,5 +253,15 @@ "openapi": ["openapi_spec.json", "rust_locker_open_api_spec.yml"], "api": { "maintainOrder": true + }, + "analytics": { + "gtm": { + "tagId": "GTM-PLBNKQFQ" + } + }, + "analytics": { + "mixpanel": { + "projectToken": "b00355f29d9548d1333608df71d5d53d" + } } } diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index df7e96431d..d218e4d207 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9053,6 +9053,29 @@ } } }, + "CardSpecificFeatures": { + "type": "object", + "required": [ + "three_ds", + "non_three_ds", + "supported_card_networks" + ], + "properties": { + "three_ds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "non_three_ds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "supported_card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetwork" + }, + "description": "List of supported card networks" + } + } + }, "CardToken": { "type": "object", "required": [ @@ -9233,7 +9256,6 @@ }, "Connector": { "type": "string", - "description": "A connector is an integration to fulfill payments", "enum": [ "adyenplatform", "phonypay", @@ -9317,6 +9339,7 @@ "signifyd", "plaid", "riskified", + "xendit", "zen", "zsl" ] @@ -17190,6 +17213,13 @@ } } }, + "PaymentMethodSpecificFeatures": { + "oneOf": [ + { + "$ref": "#/components/schemas/CardSpecificFeatures" + } + ] + }, "PaymentMethodStatus": { "type": "string", "description": "Payment Method Status", @@ -23241,6 +23271,16 @@ "description": "The code for the error", "nullable": true }, + "unified_code": { + "type": "string", + "description": "Error code unified across the connectors is received here if there was an error while calling connector", + "nullable": true + }, + "unified_message": { + "type": "string", + "description": "Error message unified across the connectors is received here if there was an error while calling connector", + "nullable": true + }, "created_at": { "type": "string", "format": "date-time", @@ -23888,7 +23928,7 @@ }, "RoutableConnectors": { "type": "string", - "description": "Connectors eligible for payments routing", + "description": "RoutableConnectors are the subset of Connectors that are eligible for payments routing", "enum": [ "adyenplatform", "phonypay", @@ -25162,54 +25202,76 @@ } ], "nullable": true + }, + "specificity_level": { + "$ref": "#/components/schemas/SuccessRateSpecificityLevel" } } }, + "SuccessRateSpecificityLevel": { + "type": "string", + "enum": [ + "merchant", + "global" + ] + }, "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, + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodSpecificFeatures" + } + ], "nullable": true }, - "supported_currencies": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Currency" - }, - "uniqueItems": true, - "nullable": true + { + "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 + } + } } - } + ] }, "SurchargeDetailsResponse": { "type": "object", diff --git a/config/config.example.toml b/config/config.example.toml index 9992663211..c860420da3 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -23,11 +23,10 @@ certificate = "/path/to/certificate.pem" # Proxy server configuration for connecting to payment gateways. # Don't define the fields if a Proxy isn't needed. Empty strings will cause failure. [proxy] -# http_url = "http proxy url" # Proxy all HTTP traffic via this proxy -# https_url = "https proxy url" # Proxy all HTTPS traffic via this proxy -idle_pool_connection_timeout = 90 # Timeout for idle pool connections (defaults to 90s) -bypass_proxy_urls = [] # A list of URLs that should bypass the proxy - +# http_url = "http proxy url" # Proxy all HTTP traffic via this proxy +# https_url = "https proxy url" # Proxy all HTTPS traffic via this proxy +idle_pool_connection_timeout = 90 # Timeout for idle pool connections (defaults to 90s) +bypass_proxy_hosts = "localhost, cluster.local" # A comma-separated list of domains or IP addresses that should not use the proxy. Whitespace between entries would be ignored. # Configuration for the Key Manager Service [key_manager] @@ -468,8 +467,8 @@ bank_redirect.giropay = { connector_list = "adyen,globalpay" } [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 +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 # Required fields info used while listing the payment_method_data [required_fields.pay_later] # payment_method = "pay_later" @@ -762,7 +761,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false -global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} [multitenancy.tenants.public] base_url = "http://localhost:8080" # URL of the tenant @@ -803,7 +802,7 @@ check_token_status_url= "" # base url to check token status from token servic connector_list = "cybersource" # Supported connectors for network tokenization [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" # Supported connectors for network transaction id +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" # Supported connectors for network transaction id [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 967b847dae..84550922d4 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -195,9 +195,9 @@ pm_auth_key = "pm_auth_key" # Payment method auth key used for authorization redis_expiry = 900 # Redis expiry time in milliseconds [proxy] -http_url = "http://proxy_http_url" # Outgoing proxy http URL to proxy the HTTP traffic -https_url = "https://proxy_https_url" # Outgoing proxy https URL to proxy the HTTPS traffic -bypass_proxy_urls = [] # A list of URLs that should bypass the proxy +http_url = "http://proxy_http_url" # Proxy all HTTP traffic via this proxy +https_url = "https://proxy_https_url" # Proxy all HTTPS traffic via this proxy +bypass_proxy_hosts = "localhost, cluster.local" # A comma-separated list of domains or IP addresses that should not use the proxy. Whitespace between entries would be ignored. # Redis credentials [redis] @@ -303,11 +303,11 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data. [multitenancy] enabled = false -global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} [multitenancy.tenants.public] -base_url = "http://localhost:8080" -schema = "public" +base_url = "http://localhost:8080" +schema = "public" redis_key_prefix = "" clickhouse_database = "default" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 6283382258..b6e487e5a3 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [payouts] diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index fcfadb339d..7242f26367 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [payouts] diff --git a/config/development.toml b/config/development.toml index 4c9b8516b5..a32102aeeb 100644 --- a/config/development.toml +++ b/config/development.toml @@ -229,8 +229,8 @@ 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/" -fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" -fiuu.third_base_url="https://api.merchant.razer.com/" +fiuu.secondary_base_url = "https://sandbox.merchant.razer.com/" +fiuu.third_base_url = "https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -241,7 +241,7 @@ 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" +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/" @@ -259,7 +259,7 @@ nuvei.base_url = "https://ppp-test.nuvei.com/" opayo.base_url = "https://pi-test.sagepay.com/" opennode.base_url = "https://dev-api.opennode.com" paybox.base_url = "https://preprod-ppps.paybox.com/PPPS.php" -paybox.secondary_base_url="https://preprod-tpeweb.paybox.com/" +paybox.secondary_base_url = "https://preprod-tpeweb.paybox.com/" payeezy.base_url = "https://api-cert.payeezy.com/" payme.base_url = "https://sandbox.payme.io/" payone.base_url = "https://payment.preprod.payone.com/" @@ -343,7 +343,7 @@ adyen = { banks = "bank_austria,bawag_psk_ag,dolomitenbank,easybank_ag,erste_ban [bank_config.ideal] stripe = { banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot" } adyen = { banks = "abn_amro,asn_bank,bunq,ing,knab,n26,nationale_nederlanden,rabobank,regiobank,revolut,sns_bank,triodos_bank,van_lanschot, yoursafe" } -multisafepay = { banks="abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" } +multisafepay = { banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" } [bank_config.online_banking_czech_republic] adyen = { banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" } @@ -395,10 +395,10 @@ cashapp = { country = "US", currency = "USD" } 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" } [pm_filters.razorpay] -upi_collect = {country = "IN", currency = "INR"} +upi_collect = { country = "IN", currency = "INR" } [pm_filters.plaid] -open_banking_pis = {currency = "EUR,GBP"} +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" } @@ -483,11 +483,11 @@ credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,B 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"} +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" } @@ -526,7 +526,6 @@ credit = { not_available_flows = { capture_method = "manual" } } debit = { not_available_flows = { capture_method = "manual" } } - [pm_filters.mollie] credit = { not_available_flows = { capture_method = "manual" } } debit = { not_available_flows = { capture_method = "manual" } } @@ -566,7 +565,7 @@ credit = { currency = "USD" } debit = { currency = "USD" } [pm_filters.fiuu] -duit_now = { country ="MY", currency = "MYR" } +duit_now = { country = "MY", currency = "MYR" } [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } @@ -623,16 +622,16 @@ connectors_with_delayed_session_response = "trustpay,payme" connectors_with_webhook_source_verification_call = "paypal" [mandates.supported_payment_methods] -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" +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" @@ -641,20 +640,20 @@ 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" +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" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] @@ -697,7 +696,7 @@ merchant_name = "HyperSwitch" card = "credit,debit" [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" } +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" } [payout_method_filters.stripe] ach = { country = "US", currency = "USD" } @@ -725,7 +724,7 @@ source = "logs" [events.kafka] brokers = ["localhost:9092"] -fraud_check_analytics_topic= "hyperswitch-fraud-check-events" +fraud_check_analytics_topic = "hyperswitch-fraud-check-events" intent_analytics_topic = "hyperswitch-payment-intent-events" attempt_analytics_topic = "hyperswitch-payment-attempt-events" refund_analytics_topic = "hyperswitch-refund-events" @@ -794,7 +793,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false -global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "global", clickhouse_database = "default" } [multitenancy.tenants.public] base_url = "http://localhost:8080" @@ -803,7 +802,7 @@ redis_key_prefix = "" clickhouse_database = "default" [multitenancy.tenants.public.user] -control_center_url = "http://localhost:9000" +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 75699d0a96..4296a5931d 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -55,9 +55,9 @@ master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e1 password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" -base_url = "http://localhost:8080" +base_url = "http://localhost:9000" force_two_factor_auth = false -force_cookies = true +force_cookies = false [locker] host = "" @@ -540,7 +540,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_customer] connector_list = "gocardless,stax,stripe" @@ -572,6 +572,7 @@ authentication_analytics_topic = "hyperswitch-authentication-events" [analytics] source = "sqlx" +forex_enabled = false # Enable or disable forex conversion for analytics [analytics.clickhouse] username = "default" @@ -635,7 +636,7 @@ sdk_eligible_payment_methods = "card" [multitenancy] enabled = false -global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default" } +global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } [multitenancy.tenants.public] base_url = "http://localhost:8080" @@ -695,7 +696,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 = "NO_EMAIL_CLIENT" # The currently active email client +active_email_client = "SMTP" # 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 @@ -707,6 +708,13 @@ sts_role_session_name = "" # An identifier for the assumed role session, used to [theme.storage] file_storage_backend = "file_system" # Theme storage backend to be used +# Configuration for smtp, applicable when the active email client is SMTP +[email.smtp] +host = "mailhog" # SMTP host +port = 1025 # SMTP port +timeout = 10 # Timeout for SMTP connection in seconds +connection = "plaintext" #currently plaintext and starttls are supported + [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 diff --git a/config/payment_required_fields_v2.toml b/config/payment_required_fields_v2.toml new file mode 100644 index 0000000000..8cc5e62c35 --- /dev/null +++ b/config/payment_required_fields_v2.toml @@ -0,0 +1,16 @@ +[[required_fields.card.credit.fields.stripe.common]] +required_field = "payment_method_data.card.card_number" +display_name = "card_number" +field_type = "user_card_number" +[[required_fields.card.credit.fields.stripe.common]] +required_field = "payment_method_data.card.card_exp_year" +display_name = "card_exp_year" +field_type = "user_card_expiry_year" +[[required_fields.card.credit.fields.stripe.common]] +required_field = "payment_method_data.card.card_cvc" +display_name = "card_cvc" +field_type = "user_card_cvc" +[[required_fields.card.credit.fields.stripe.common]] +required_field = "payment_method_data.card.card_exp_month" +display_name = "card_exp_month" +field_type = "user_card_expiry_month" diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index ef25a4350d..321ee5305f 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1009,9 +1009,10 @@ pub struct MerchantConnectorResponse { /// 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: String, + pub connector_name: common_enums::connector_enums::Connector, /// A unique label to identify the connector account created under a profile #[schema(example = "stripe_US_travel")] @@ -1310,9 +1311,10 @@ pub struct MerchantConnectorListResponse { /// 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: String, + pub connector_name: common_enums::connector_enums::Connector, /// A unique label to identify the connector account created under a profile #[schema(example = "stripe_US_travel")] @@ -1862,8 +1864,9 @@ pub struct ProfileCreate { 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>, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[nutype::nutype( @@ -1979,8 +1982,9 @@ pub struct ProfileCreate { 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>, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -2115,8 +2119,9 @@ pub struct ProfileResponse { 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, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] @@ -2238,8 +2243,9 @@ pub struct ProfileResponse { 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, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -2368,8 +2374,9 @@ pub struct ProfileUpdate { 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>, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] @@ -2479,8 +2486,9 @@ pub struct ProfileUpdate { 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>, + #[schema(value_type = Option, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)] + pub authentication_product_ids: + Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index 3a3fe5e8f4..668c62c0c3 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -1,320 +1 @@ -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(()) - } - } -} +pub use common_enums::connector_enums::Connector; diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 72a0d59251..27cbbd614d 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -179,16 +179,6 @@ impl ApiEventMetric for DisputesMetricsResponse { 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_type), - payment_method_type: Some(self.payment_method_subtype), - }) - } -} #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl ApiEventMetric for PaymentMethodIntentCreate { diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index c242788e09..e6de6190b8 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -14,11 +14,11 @@ 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, - PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, + self, ListCountriesCurrenciesRequest, ListCountriesCurrenciesResponse, + PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, + PaymentMethodCollectLinkResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, + PaymentMethodListResponse, PaymentMethodMigrateResponse, PaymentMethodResponse, + PaymentMethodUpdate, }, payments::{ self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, @@ -211,9 +211,9 @@ impl ApiEventMetric for PaymentMethodResponse { #[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, + payment_method_id: self.id.clone(), + payment_method_type: self.payment_method_type, + payment_method_subtype: self.payment_method_subtype, }) } } @@ -234,16 +234,17 @@ impl ApiEventMetric for PaymentMethodMigrateResponse { #[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, + payment_method_id: self.payment_method_response.id.clone(), + payment_method_type: self.payment_method_response.payment_method_type, + payment_method_subtype: self.payment_method_response.payment_method_subtype, }) } } impl ApiEventMetric for PaymentMethodUpdate {} -impl ApiEventMetric for DefaultPaymentMethod { +#[cfg(feature = "v1")] +impl ApiEventMetric for payment_methods::DefaultPaymentMethod { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::PaymentMethod { payment_method_id: self.payment_method_id.clone(), @@ -253,6 +254,18 @@ impl ApiEventMetric for DefaultPaymentMethod { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentMethodDeleteResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.id.clone(), + payment_method_type: None, + payment_method_subtype: None, + }) + } +} + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentMethodDeleteResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::PaymentMethod { @@ -282,7 +295,8 @@ impl ApiEventMetric for ListCountriesCurrenciesRequest {} impl ApiEventMetric for ListCountriesCurrenciesResponse {} impl ApiEventMetric for PaymentMethodListResponse {} -impl ApiEventMetric for CustomerDefaultPaymentMethodResponse { +#[cfg(feature = "v1")] +impl ApiEventMetric for payment_methods::CustomerDefaultPaymentMethodResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::PaymentMethod { payment_method_id: self.default_payment_method_id.clone().unwrap_or_default(), diff --git a/crates/api_models/src/feature_matrix.rs b/crates/api_models/src/feature_matrix.rs index 2eaade1b61..c1eb699705 100644 --- a/crates/api_models/src/feature_matrix.rs +++ b/crates/api_models/src/feature_matrix.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use crate::enums::{ - CaptureMethod, Connector, CountryAlpha2, Currency, EventClass, FeatureStatus, + CaptureMethod, CardNetwork, Connector, CountryAlpha2, Currency, EventClass, FeatureStatus, PaymentConnectorCategory, PaymentMethod, PaymentMethodType, }; @@ -14,6 +14,23 @@ pub struct FeatureMatrixRequest { pub connectors: Option>, } +#[derive(Debug, Clone, ToSchema, Serialize)] +pub struct CardSpecificFeatures { + /// Indicates whether three_ds card payments are supported. + pub three_ds: FeatureStatus, + /// Indicates whether non three_ds card payments are supported. + pub non_three_ds: FeatureStatus, + /// List of supported card networks + pub supported_card_networks: Vec, +} + +#[derive(Debug, Clone, ToSchema, Serialize)] +#[serde(untagged)] +pub enum PaymentMethodSpecificFeatures { + /// Card specific features + Card(CardSpecificFeatures), +} + #[derive(Debug, ToSchema, Serialize)] pub struct SupportedPaymentMethod { pub payment_method: PaymentMethod, @@ -21,6 +38,8 @@ pub struct SupportedPaymentMethod { pub mandates: FeatureStatus, pub refunds: FeatureStatus, pub supported_capture_methods: Vec, + #[serde(flatten)] + pub payment_method_specific_features: Option, pub supported_countries: Option>, pub supported_currencies: Option>, } diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 18d18f08fd..82a7abc6ef 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -194,39 +194,6 @@ impl PaymentMethodIntentConfirm { } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] -#[serde(deny_unknown_fields)] -pub struct PaymentMethodIntentConfirmInternal { - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub id: String, - /// The type of payment method use for the payment. - #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method_type: api_enums::PaymentMethod, - - /// This is a sub-category of payment method. - #[schema(value_type = PaymentMethodType,example = "credit")] - 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")] - pub customer_id: Option, - - /// Payment method data to be passed - pub payment_method_data: PaymentMethodCreateData, -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl From for PaymentMethodIntentConfirm { - fn from(item: PaymentMethodIntentConfirmInternal) -> Self { - Self { - 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(), - } - } -} #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] /// This struct is only used by and internal api to migrate payment method pub struct PaymentMethodMigrate { @@ -776,6 +743,10 @@ pub struct PaymentMethodResponse { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema, Clone)] pub struct PaymentMethodResponse { + /// The unique identifier of the Payment method + #[schema(value_type = String, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub id: id_type::GlobalPaymentMethodId, + /// Unique identifier for a merchant #[schema(value_type = String, example = "merchant_1671528864")] pub merchant_id: id_type::MerchantId, @@ -789,10 +760,6 @@ pub struct PaymentMethodResponse { )] pub customer_id: id_type::GlobalCustomerId, - /// The unique identifier of the Payment method - #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] - pub payment_method_id: String, - /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod, example = "card")] pub payment_method_type: Option, @@ -1043,6 +1010,13 @@ impl From for payments::AdditionalCardInfo { } } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentMethodListResponse { + /// The list of payment methods that are enabled for the business profile + pub payment_methods_enabled: Vec, +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -1246,19 +1220,19 @@ pub struct ResponsePaymentMethodTypes { #[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] #[serde(untagged)] // Untagged used for serialization only pub enum PaymentMethodSubtypeSpecificData { + #[schema(title = "card")] Card { card_networks: Vec, }, - Bank { - bank_names: Vec, - }, + #[schema(title = "bank")] + 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)] + #[schema(example = "pay_later", value_type = PaymentMethod)] pub payment_method_type: common_enums::PaymentMethod, /// The payment method subtype enabled @@ -1271,10 +1245,7 @@ pub struct ResponsePaymentMethodTypes { /// 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, + pub required_fields: Vec, } #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] @@ -1678,6 +1649,7 @@ fn set_or_reject_duplicate( } } +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, ToSchema)] pub struct PaymentMethodListResponse { /// Redirect URL of the merchant @@ -1758,9 +1730,11 @@ pub struct PaymentMethodDeleteResponse { #[derive(Debug, serde::Serialize, ToSchema)] pub struct PaymentMethodDeleteResponse { /// The unique identifier of the Payment method - #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] - pub payment_method_id: String, + #[schema(value_type = String, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub id: id_type::GlobalPaymentMethodId, } + +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, ToSchema)] pub struct CustomerDefaultPaymentMethodResponse { /// The unique identifier of the Payment method @@ -2046,12 +2020,14 @@ pub struct PaymentMethodId { pub payment_method_id: String, } +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct DefaultPaymentMethod { #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub customer_id: id_type::CustomerId, pub payment_method_id: String, } + //------------------------------------------------TokenizeService------------------------------------------------ #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TokenizePayloadEncrypted { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 816c085b8f..b6152cc978 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6558,8 +6558,7 @@ pub struct PaymentMethodsListRequest {} #[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, + 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 @@ -6567,6 +6566,32 @@ pub struct PaymentMethodListResponseForPayments { pub customer_payment_methods: Option>, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] +pub struct ResponsePaymentMethodTypesForPayments { + /// The payment method type enabled + #[schema(example = "pay_later", value_type = PaymentMethod)] + 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)] + #[schema(value_type = Option)] + 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. + #[schema(value_type = Option)] + pub required_fields: Option>, + + /// surcharge details for this payment method type if exists + #[schema(value_type = Option)] + pub surcharge_details: Option, +} + #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct PaymentsExternalAuthenticationResponse { /// Indicates the transaction status diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 36a92e3ab0..ad09c333ea 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -188,6 +188,10 @@ pub struct RefundResponse { pub error_message: Option, /// The code for the error pub error_code: Option, + /// Error code unified across the connectors is received here if there was an error while calling connector + pub unified_code: Option, + /// Error message unified across the connectors is received here if there was an error while calling connector + pub unified_message: Option, /// The timestamp at which refund is created #[serde(with = "common_utils::custom_serde::iso8601::option")] pub created_at: Option, diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 2e570816ab..8e50276757 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -779,6 +779,7 @@ impl Default for SuccessBasedRoutingConfig { duration_in_mins: Some(5), max_total_count: Some(2), }), + specificity_level: SuccessRateSpecificityLevel::default(), }), } } @@ -801,6 +802,8 @@ pub struct SuccessBasedRoutingConfigBody { pub default_success_rate: Option, pub max_aggregates_size: Option, pub current_block_threshold: Option, + #[serde(default)] + pub specificity_level: SuccessRateSpecificityLevel, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] @@ -809,6 +812,14 @@ pub struct CurrentBlockThreshold { pub max_total_count: Option, } +#[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum SuccessRateSpecificityLevel { + #[default] + Merchant, + Global, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SuccessBasedRoutingPayloadWrapper { pub updated_config: SuccessBasedRoutingConfig, @@ -849,6 +860,7 @@ impl SuccessBasedRoutingConfigBody { .as_mut() .map(|threshold| threshold.update(current_block_threshold)); } + self.specificity_level = new.specificity_level } } diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index e6f4065eb7..f99e3a450b 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -120,6 +120,10 @@ pub enum WebhookResponseTracker { status: common_enums::MandateStatus, }, NoEffect, + Relay { + relay_id: common_utils::id_type::RelayId, + status: common_enums::RelayStatus, + }, } impl WebhookResponseTracker { @@ -132,6 +136,7 @@ impl WebhookResponseTracker { Self::NoEffect | Self::Mandate { .. } => None, #[cfg(feature = "payouts")] Self::Payout { .. } => None, + Self::Relay { .. } => None, } } @@ -144,6 +149,7 @@ impl WebhookResponseTracker { Self::NoEffect | Self::Mandate { .. } => None, #[cfg(feature = "payouts")] Self::Payout { .. } => None, + Self::Relay { .. } => None, } } } diff --git a/crates/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index 92fc2f0206..5db5393644 100644 --- a/crates/common_enums/Cargo.toml +++ b/crates/common_enums/Cargo.toml @@ -13,7 +13,7 @@ openapi = [] payouts = [] [dependencies] -diesel = { version = "2.2.3", features = ["postgres"] } +diesel = { version = "2.2.3", features = ["postgres", "128-column-tables"] } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" strum = { version = "0.26", features = ["derive"] } diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 127d5a9901..0402ae0645 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -1,4 +1,7 @@ use utoipa::ToSchema; + +pub use super::enums::{PaymentMethod, PayoutType}; + #[derive( Clone, Copy, @@ -17,7 +20,7 @@ use utoipa::ToSchema; #[router_derive::diesel_enum(storage_type = "db_enum")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] -/// Connectors eligible for payments routing +/// RoutableConnectors are the subset of Connectors that are eligible for payments routing pub enum RoutableConnectors { Adyenplatform, #[cfg(feature = "dummy_connector")] @@ -132,3 +135,408 @@ pub enum RoutableConnectors { Plaid, Zsl, } + +// 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, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[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) + } + + /// Validates if dummy connector can be created + /// Dummy connectors can be created only if dummy_connector feature is enabled in the configs + #[cfg(feature = "dummy_connector")] + pub fn validate_dummy_connector_create(self, is_dummy_connector_enabled: bool) -> bool { + matches!( + self, + Self::DummyConnector1 + | Self::DummyConnector2 + | Self::DummyConnector3 + | Self::DummyConnector4 + | Self::DummyConnector5 + | Self::DummyConnector6 + | Self::DummyConnector7 + ) && !is_dummy_connector_enabled + } +} + +/// Convert the RoutableConnectors to Connector +impl From for Connector { + fn from(routable_connector: RoutableConnectors) -> Self { + match routable_connector { + RoutableConnectors::Adyenplatform => Self::Adyenplatform, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector1 => Self::DummyConnector1, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector2 => Self::DummyConnector2, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector3 => Self::DummyConnector3, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector4 => Self::DummyConnector4, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector5 => Self::DummyConnector5, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector6 => Self::DummyConnector6, + #[cfg(feature = "dummy_connector")] + RoutableConnectors::DummyConnector7 => Self::DummyConnector7, + RoutableConnectors::Aci => Self::Aci, + RoutableConnectors::Adyen => Self::Adyen, + RoutableConnectors::Airwallex => Self::Airwallex, + RoutableConnectors::Authorizedotnet => Self::Authorizedotnet, + RoutableConnectors::Bankofamerica => Self::Bankofamerica, + RoutableConnectors::Billwerk => Self::Billwerk, + RoutableConnectors::Bitpay => Self::Bitpay, + RoutableConnectors::Bambora => Self::Bambora, + RoutableConnectors::Bamboraapac => Self::Bamboraapac, + RoutableConnectors::Bluesnap => Self::Bluesnap, + RoutableConnectors::Boku => Self::Boku, + RoutableConnectors::Braintree => Self::Braintree, + RoutableConnectors::Cashtocode => Self::Cashtocode, + RoutableConnectors::Checkout => Self::Checkout, + RoutableConnectors::Coinbase => Self::Coinbase, + RoutableConnectors::Cryptopay => Self::Cryptopay, + RoutableConnectors::Cybersource => Self::Cybersource, + RoutableConnectors::Datatrans => Self::Datatrans, + RoutableConnectors::Deutschebank => Self::Deutschebank, + RoutableConnectors::Digitalvirgo => Self::Digitalvirgo, + RoutableConnectors::Dlocal => Self::Dlocal, + RoutableConnectors::Ebanx => Self::Ebanx, + RoutableConnectors::Elavon => Self::Elavon, + RoutableConnectors::Fiserv => Self::Fiserv, + RoutableConnectors::Fiservemea => Self::Fiservemea, + RoutableConnectors::Fiuu => Self::Fiuu, + RoutableConnectors::Forte => Self::Forte, + RoutableConnectors::Globalpay => Self::Globalpay, + RoutableConnectors::Globepay => Self::Globepay, + RoutableConnectors::Gocardless => Self::Gocardless, + RoutableConnectors::Helcim => Self::Helcim, + RoutableConnectors::Iatapay => Self::Iatapay, + RoutableConnectors::Itaubank => Self::Itaubank, + RoutableConnectors::Jpmorgan => Self::Jpmorgan, + RoutableConnectors::Klarna => Self::Klarna, + RoutableConnectors::Mifinity => Self::Mifinity, + RoutableConnectors::Mollie => Self::Mollie, + RoutableConnectors::Multisafepay => Self::Multisafepay, + RoutableConnectors::Nexinets => Self::Nexinets, + RoutableConnectors::Nexixpay => Self::Nexixpay, + RoutableConnectors::Nmi => Self::Nmi, + RoutableConnectors::Noon => Self::Noon, + RoutableConnectors::Novalnet => Self::Novalnet, + RoutableConnectors::Nuvei => Self::Nuvei, + RoutableConnectors::Opennode => Self::Opennode, + RoutableConnectors::Paybox => Self::Paybox, + RoutableConnectors::Payme => Self::Payme, + RoutableConnectors::Payone => Self::Payone, + RoutableConnectors::Paypal => Self::Paypal, + RoutableConnectors::Payu => Self::Payu, + RoutableConnectors::Placetopay => Self::Placetopay, + RoutableConnectors::Powertranz => Self::Powertranz, + RoutableConnectors::Prophetpay => Self::Prophetpay, + RoutableConnectors::Rapyd => Self::Rapyd, + RoutableConnectors::Razorpay => Self::Razorpay, + RoutableConnectors::Riskified => Self::Riskified, + RoutableConnectors::Shift4 => Self::Shift4, + RoutableConnectors::Signifyd => Self::Signifyd, + RoutableConnectors::Square => Self::Square, + RoutableConnectors::Stax => Self::Stax, + RoutableConnectors::Stripe => Self::Stripe, + RoutableConnectors::Trustpay => Self::Trustpay, + RoutableConnectors::Tsys => Self::Tsys, + RoutableConnectors::Volt => Self::Volt, + RoutableConnectors::Wellsfargo => Self::Wellsfargo, + RoutableConnectors::Wise => Self::Wise, + RoutableConnectors::Worldline => Self::Worldline, + RoutableConnectors::Worldpay => Self::Worldpay, + RoutableConnectors::Zen => Self::Zen, + RoutableConnectors::Plaid => Self::Plaid, + RoutableConnectors::Zsl => Self::Zsl, + RoutableConnectors::Xendit => Self::Xendit, + } + } +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 0833ab37e3..562b4fdf26 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -63,6 +63,8 @@ pub enum ApiClientError { #[error("Unexpected state reached/Invariants conflicted")] UnexpectedState, + #[error("Failed to parse URL")] + UrlParsingFailed, #[error("URL encoding of request payload failed")] UrlEncodingFailed, #[error("Failed to send request to connector {0}")] @@ -1304,6 +1306,20 @@ pub enum IntentStatus { } impl IntentStatus { + /// Indicates whether the payment intent is in terminal state or not + pub fn is_in_terminal_state(self) -> bool { + match self { + Self::Succeeded | Self::Failed | Self::Cancelled | Self::PartiallyCaptured => true, + Self::Processing + | Self::RequiresCustomerAction + | Self::RequiresMerchantAction + | Self::RequiresPaymentMethod + | Self::RequiresConfirmation + | Self::RequiresCapture + | Self::PartiallyCapturedAndCapturable => false, + } + } + /// Indicates whether the syncing with the connector should be allowed or not pub fn should_force_sync_with_connector(self) -> bool { match self { @@ -3575,12 +3591,33 @@ pub enum StripeChargeType { Destination, } +/// Authentication Products +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum AuthenticationProduct { + ClickToPay, +} + /// Connector Access Method #[derive( Clone, Copy, Debug, Eq, + Hash, PartialEq, serde::Deserialize, serde::Serialize, diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index 0eef7ecaf2..0807560562 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -1,7 +1,9 @@ //! Payment related types +use std::collections::HashMap; + use common_enums::enums; -use common_utils::{impl_to_sql_from_sql_json, types::MinorUnit}; +use common_utils::{errors, impl_to_sql_from_sql_json, types::MinorUnit}; use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -38,3 +40,28 @@ pub struct StripeSplitPaymentRequest { pub transfer_account_id: String, } impl_to_sql_from_sql_json!(StripeSplitPaymentRequest); + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Hashmap to store mca_id's with product names +pub struct AuthenticationConnectorAccountMap( + HashMap, +); +impl_to_sql_from_sql_json!(AuthenticationConnectorAccountMap); + +impl AuthenticationConnectorAccountMap { + /// fn to get click to pay connector_account_id + pub fn get_click_to_pay_connector_account_id( + &self, + ) -> Result { + self.0 + .get(&enums::AuthenticationProduct::ClickToPay) + .ok_or(errors::ValidationError::MissingRequiredField { + field_name: "authentication_product_id.click_to_pay".to_string(), + }) + .cloned() + } +} diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 3b437b703b..fdda26cc77 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -149,3 +149,6 @@ pub const APPLEPAY_VALIDATION_URL: &str = /// Request ID pub const X_REQUEST_ID: &str = "x-request-id"; + +/// Default Tenant ID for the `Global` tenant +pub const DEFAULT_GLOBAL_TENANT_ID: &str = "global"; diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 9494b8209e..bae525f456 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -1,4 +1,3 @@ -use common_enums::{PaymentMethod, PaymentMethodType}; use serde::Serialize; use crate::{id_type, types::TimeRange}; @@ -33,10 +32,17 @@ pub enum ApiEventsType { payment_id: id_type::GlobalPaymentId, refund_id: id_type::GlobalRefundId, }, + #[cfg(feature = "v1")] PaymentMethod { payment_method_id: String, - payment_method: Option, - payment_method_type: Option, + payment_method: Option, + payment_method_type: Option, + }, + #[cfg(feature = "v2")] + PaymentMethod { + payment_method_id: id_type::GlobalPaymentMethodId, + payment_method_type: Option, + payment_method_subtype: Option, }, #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] PaymentMethodCreate, @@ -60,6 +66,10 @@ pub enum ApiEventsType { PaymentMethodList { payment_id: Option, }, + #[cfg(feature = "v2")] + PaymentMethodListForPaymentMethods { + payment_method_id: id_type::GlobalPaymentMethodId, + }, #[cfg(feature = "v1")] Webhooks { connector: String, 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 40bd6ec0df..a4fec5e836 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 @@ -1,9 +1,8 @@ use error_stack::ResultExt; use crate::{ - errors, errors::CustomResult, - id_type::global_id::{CellId, GlobalEntity, GlobalId, GlobalIdError}, + id_type::global_id::{CellId, GlobalEntity, GlobalId}, }; /// A global id that can be used to identify a payment method @@ -26,6 +25,16 @@ pub enum GlobalPaymentMethodIdError { ConstructionError, } +impl crate::events::ApiEventMetric for GlobalPaymentMethodId { + fn get_api_event_type(&self) -> Option { + Some( + crate::events::ApiEventsType::PaymentMethodListForPaymentMethods { + payment_method_id: self.clone(), + }, + ) + } +} + impl GlobalPaymentMethodId { /// Create a new GlobalPaymentMethodId from cell id information pub fn generate(cell_id: &CellId) -> error_stack::Result { diff --git a/crates/common_utils/src/id_type/relay.rs b/crates/common_utils/src/id_type/relay.rs index 3ad64729fb..c818671e0e 100644 --- a/crates/common_utils/src/id_type/relay.rs +++ b/crates/common_utils/src/id_type/relay.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + crate::id_type!( RelayId, "A type for relay_id that can be used for relay ids" @@ -11,3 +13,12 @@ crate::impl_queryable_id_type!(RelayId); crate::impl_to_sql_from_sql_id_type!(RelayId); crate::impl_debug_id_type!(RelayId); + +impl FromStr for RelayId { + 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) + } +} diff --git a/crates/common_utils/src/id_type/tenant.rs b/crates/common_utils/src/id_type/tenant.rs index 953bf82287..0584496332 100644 --- a/crates/common_utils/src/id_type/tenant.rs +++ b/crates/common_utils/src/id_type/tenant.rs @@ -1,4 +1,7 @@ -use crate::errors::{CustomResult, ValidationError}; +use crate::{ + consts::DEFAULT_GLOBAL_TENANT_ID, + errors::{CustomResult, ValidationError}, +}; crate::id_type!( TenantId, @@ -15,6 +18,13 @@ crate::impl_queryable_id_type!(TenantId); crate::impl_to_sql_from_sql_id_type!(TenantId); impl TenantId { + /// Get the default global tenant ID + pub fn get_default_global_tenant_id() -> Self { + Self(super::LengthId::new_unchecked( + super::AlphaNumericId::new_unchecked(DEFAULT_GLOBAL_TENANT_ID.to_string()), + )) + } + /// 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/keymanager.rs b/crates/common_utils/src/keymanager.rs index 5376195179..0dc05b22fd 100644 --- a/crates/common_utils/src/keymanager.rs +++ b/crates/common_utils/src/keymanager.rs @@ -11,11 +11,11 @@ use once_cell::sync::OnceCell; use router_env::{instrument, logger, tracing}; use crate::{ - consts::BASE64_ENGINE, + consts::{BASE64_ENGINE, TENANT_HEADER}, errors, types::keymanager::{ BatchDecryptDataRequest, DataKeyCreateResponse, DecryptDataRequest, - EncryptionCreateRequest, EncryptionTransferRequest, KeyManagerState, + EncryptionCreateRequest, EncryptionTransferRequest, GetKeymanagerTenant, KeyManagerState, TransientBatchDecryptDataRequest, TransientDecryptDataRequest, }, }; @@ -100,7 +100,7 @@ pub async fn call_encryption_service( request_body: T, ) -> errors::CustomResult where - T: ConvertRaw + Send + Sync + 'static + Debug, + T: GetKeymanagerTenant + ConvertRaw + Send + Sync + 'static + Debug, R: serde::de::DeserializeOwned, { let url = format!("{}/{endpoint}", &state.url); @@ -122,6 +122,15 @@ where .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, )) } + + //Add Tenant ID + header.push(( + HeaderName::from_str(TENANT_HEADER) + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + HeaderValue::from_str(request_body.get_tenant_id(state).get_string_repr()) + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + )); + let response = send_encryption_request( state, HeaderMap::from_iter(header.into_iter()), diff --git a/crates/common_utils/src/types/keymanager.rs b/crates/common_utils/src/types/keymanager.rs index 09d26bd91e..f18c665620 100644 --- a/crates/common_utils/src/types/keymanager.rs +++ b/crates/common_utils/src/types/keymanager.rs @@ -23,8 +23,23 @@ use crate::{ transformers::{ForeignFrom, ForeignTryFrom}, }; +macro_rules! impl_get_tenant_for_request { + ($ty:ident) => { + impl GetKeymanagerTenant for $ty { + fn get_tenant_id(&self, state: &KeyManagerState) -> id_type::TenantId { + match self.identifier { + Identifier::User(_) | Identifier::UserAuth(_) => state.global_tenant_id.clone(), + Identifier::Merchant(_) => state.tenant_id.clone(), + } + } + } + }; +} + #[derive(Debug, Clone)] pub struct KeyManagerState { + pub tenant_id: id_type::TenantId, + pub global_tenant_id: id_type::TenantId, pub enabled: bool, pub url: String, pub client_idle_timeout: Option, @@ -35,6 +50,11 @@ pub struct KeyManagerState { #[cfg(feature = "keymanager_mtls")] pub cert: Secret, } + +pub trait GetKeymanagerTenant { + fn get_tenant_id(&self, state: &KeyManagerState) -> id_type::TenantId; +} + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] #[serde(tag = "data_identifier", content = "key_identifier")] pub enum Identifier { @@ -70,6 +90,10 @@ pub struct BatchEncryptDataRequest { pub data: DecryptedDataGroup, } +impl_get_tenant_for_request!(EncryptionCreateRequest); +impl_get_tenant_for_request!(EncryptionTransferRequest); +impl_get_tenant_for_request!(BatchEncryptDataRequest); + impl From<(Secret, S>, Identifier)> for EncryptDataRequest where S: Strategy>, @@ -219,6 +243,12 @@ pub struct DecryptDataRequest { pub data: StrongSecret, } +impl_get_tenant_for_request!(EncryptDataRequest); +impl_get_tenant_for_request!(TransientBatchDecryptDataRequest); +impl_get_tenant_for_request!(TransientDecryptDataRequest); +impl_get_tenant_for_request!(BatchDecryptDataRequest); +impl_get_tenant_for_request!(DecryptDataRequest); + impl ForeignFrom<(FxHashMap>, BatchEncryptDataResponse)> for FxHashMap>> where diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index e7d83ca14c..791a0679ea 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -414,6 +414,7 @@ impl ConnectorConfig { Connector::DummyConnector7 => Ok(connector_data.paypal_test), Connector::Netcetera => Ok(connector_data.netcetera), Connector::CtpMastercard => Ok(connector_data.ctp_mastercard), + Connector::Xendit => Ok(connector_data.xendit), } } } diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index c2d577dce9..2ab348d5dd 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -4472,3 +4472,43 @@ label="Merchant Country Code" placeholder="Enter Merchant Country Code" required=true type="Text" + +[xendit] +[[xendit.credit]] + payment_method_type = "Mastercard" +[[xendit.credit]] + payment_method_type = "Visa" +[[xendit.credit]] + payment_method_type = "Interac" +[[xendit.credit]] + payment_method_type = "AmericanExpress" +[[xendit.credit]] + payment_method_type = "JCB" +[[xendit.credit]] + payment_method_type = "DinersClub" +[[xendit.credit]] + payment_method_type = "Discover" +[[xendit.credit]] + payment_method_type = "CartesBancaires" +[[xendit.credit]] + payment_method_type = "UnionPay" +[[xendit.debit]] + payment_method_type = "Mastercard" +[[xendit.debit]] + payment_method_type = "Visa" +[[xendit.debit]] + payment_method_type = "Interac" +[[xendit.debit]] + payment_method_type = "AmericanExpress" +[[xendit.debit]] + payment_method_type = "JCB" +[[xendit.debit]] + payment_method_type = "DinersClub" +[[xendit.debit]] + payment_method_type = "Discover" +[[xendit.debit]] + payment_method_type = "CartesBancaires" +[[xendit.debit]] + payment_method_type = "UnionPay" +[xendit.connector_auth.HeaderKey] +api_key="API Key" \ No newline at end of file diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index aad91f830d..35f2f14c74 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -3340,4 +3340,44 @@ merchant_secret="Source verification key" [elavon.connector_auth.SignatureKey] api_key="Account Id" key1="User ID" -api_secret="Pin" \ No newline at end of file +api_secret="Pin" + +[xendit] +[[xendit.credit]] + payment_method_type = "Mastercard" +[[xendit.credit]] + payment_method_type = "Visa" +[[xendit.credit]] + payment_method_type = "Interac" +[[xendit.credit]] + payment_method_type = "AmericanExpress" +[[xendit.credit]] + payment_method_type = "JCB" +[[xendit.credit]] + payment_method_type = "DinersClub" +[[xendit.credit]] + payment_method_type = "Discover" +[[xendit.credit]] + payment_method_type = "CartesBancaires" +[[xendit.credit]] + payment_method_type = "UnionPay" +[[xendit.debit]] + payment_method_type = "Mastercard" +[[xendit.debit]] + payment_method_type = "Visa" +[[xendit.debit]] + payment_method_type = "Interac" +[[xendit.debit]] + payment_method_type = "AmericanExpress" +[[xendit.debit]] + payment_method_type = "JCB" +[[xendit.debit]] + payment_method_type = "DinersClub" +[[xendit.debit]] + payment_method_type = "Discover" +[[xendit.debit]] + payment_method_type = "CartesBancaires" +[[xendit.debit]] + payment_method_type = "UnionPay" +[xendit.connector_auth.HeaderKey] +api_key="API Key" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index dc31ebf8f8..50c13dc859 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -4410,3 +4410,43 @@ label="Merchant Country Code" placeholder="Enter Merchant Country Code" required=true type="Text" + +[xendit] +[[xendit.credit]] + payment_method_type = "Mastercard" +[[xendit.credit]] + payment_method_type = "Visa" +[[xendit.credit]] + payment_method_type = "Interac" +[[xendit.credit]] + payment_method_type = "AmericanExpress" +[[xendit.credit]] + payment_method_type = "JCB" +[[xendit.credit]] + payment_method_type = "DinersClub" +[[xendit.credit]] + payment_method_type = "Discover" +[[xendit.credit]] + payment_method_type = "CartesBancaires" +[[xendit.credit]] + payment_method_type = "UnionPay" +[[xendit.debit]] + payment_method_type = "Mastercard" +[[xendit.debit]] + payment_method_type = "Visa" +[[xendit.debit]] + payment_method_type = "Interac" +[[xendit.debit]] + payment_method_type = "AmericanExpress" +[[xendit.debit]] + payment_method_type = "JCB" +[[xendit.debit]] + payment_method_type = "DinersClub" +[[xendit.debit]] + payment_method_type = "Discover" +[[xendit.debit]] + payment_method_type = "CartesBancaires" +[[xendit.debit]] + payment_method_type = "UnionPay" +[xendit.connector_auth.HeaderKey] +api_key="API Key" \ No newline at end of file diff --git a/crates/diesel_models/Cargo.toml b/crates/diesel_models/Cargo.toml index 5da67eb307..4f02cc4aea 100644 --- a/crates/diesel_models/Cargo.toml +++ b/crates/diesel_models/Cargo.toml @@ -17,7 +17,7 @@ payment_methods_v2 = [] [dependencies] async-bb8-diesel = { git = "https://github.com/jarnura/async-bb8-diesel", rev = "53b4ab901aab7635c8215fd1c2d542c8db443094" } -diesel = { version = "2.2.3", features = ["postgres", "serde_json", "time", "64-column-tables"] } +diesel = { version = "2.2.3", features = ["postgres", "serde_json", "time", "128-column-tables"] } error-stack = "0.4.1" rustc-hash = "1.1.0" serde = { version = "1.0.197", features = ["derive"] } diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index dff3f174fc..06aa21fe9d 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -58,7 +58,8 @@ pub struct Profile { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -103,7 +104,8 @@ pub struct ProfileNew { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -145,7 +147,8 @@ pub struct ProfileUpdateInternal { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: Option, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -305,7 +308,8 @@ pub struct Profile { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } impl Profile { @@ -365,7 +369,8 @@ pub struct ProfileNew { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] @@ -409,7 +414,8 @@ pub struct ProfileUpdateInternal { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: Option, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/callback_mapper.rs b/crates/diesel_models/src/callback_mapper.rs new file mode 100644 index 0000000000..3e031d483a --- /dev/null +++ b/crates/diesel_models/src/callback_mapper.rs @@ -0,0 +1,14 @@ +use common_utils::pii; +use diesel::{Identifiable, Insertable, Queryable, Selectable}; + +use crate::schema::callback_mapper; + +#[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Insertable)] +#[diesel(table_name = callback_mapper, primary_key(id, type_), check_for_backend(diesel::pg::Pg))] +pub struct CallbackMapper { + pub id: String, + pub type_: String, + pub data: pii::SecretSerdeValue, + pub created_at: time::PrimitiveDateTime, + pub last_modified_at: time::PrimitiveDateTime, +} diff --git a/crates/diesel_models/src/dynamic_routing_stats.rs b/crates/diesel_models/src/dynamic_routing_stats.rs index c055359d8b..90cf468908 100644 --- a/crates/diesel_models/src/dynamic_routing_stats.rs +++ b/crates/diesel_models/src/dynamic_routing_stats.rs @@ -20,6 +20,7 @@ pub struct DynamicRoutingStatsNew { pub conclusive_classification: common_enums::SuccessBasedRoutingConclusiveState, pub created_at: time::PrimitiveDateTime, pub payment_method_type: Option, + pub global_success_based_connector: Option, } #[derive(Clone, Debug, Eq, PartialEq, Queryable, Selectable, Insertable)] @@ -40,4 +41,5 @@ pub struct DynamicRoutingStats { pub conclusive_classification: common_enums::SuccessBasedRoutingConclusiveState, pub created_at: time::PrimitiveDateTime, pub payment_method_type: Option, + pub global_success_based_connector: Option, } diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index 1369368a80..1908edb8ad 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -10,6 +10,7 @@ pub mod authentication; pub mod authorization; pub mod blocklist; pub mod blocklist_fingerprint; +pub mod callback_mapper; pub mod customers; pub mod dispute; pub mod dynamic_routing_stats; @@ -61,11 +62,11 @@ use diesel_impl::{DieselArray, OptionalDieselArray}; pub type StorageResult = error_stack::Result; pub type PgPooledConn = async_bb8_diesel::Connection; pub use self::{ - address::*, api_keys::*, cards_info::*, configs::*, customers::*, dispute::*, ephemeral_key::*, - events::*, file::*, generic_link::*, locker_mock_up::*, mandate::*, merchant_account::*, - merchant_connector_account::*, payment_attempt::*, payment_intent::*, payment_method::*, - payout_attempt::*, payouts::*, process_tracker::*, refund::*, reverse_lookup::*, - user_authentication_method::*, + address::*, api_keys::*, callback_mapper::*, cards_info::*, configs::*, customers::*, + dispute::*, ephemeral_key::*, events::*, file::*, generic_link::*, locker_mock_up::*, + mandate::*, merchant_account::*, merchant_connector_account::*, payment_attempt::*, + payment_intent::*, payment_method::*, payout_attempt::*, payouts::*, process_tracker::*, + refund::*, reverse_lookup::*, user_authentication_method::*, }; /// The types and implementations provided by this module are required for the schema generated by diff --git a/crates/diesel_models/src/merchant_connector_account.rs b/crates/diesel_models/src/merchant_connector_account.rs index 1ecff0fc70..e536648a6d 100644 --- a/crates/diesel_models/src/merchant_connector_account.rs +++ b/crates/diesel_models/src/merchant_connector_account.rs @@ -73,7 +73,7 @@ impl MerchantConnectorAccount { #[diesel(table_name = merchant_connector_account, check_for_backend(diesel::pg::Pg))] pub struct MerchantConnectorAccount { pub merchant_id: id_type::MerchantId, - pub connector_name: String, + pub connector_name: common_enums::connector_enums::Connector, pub connector_account_details: Encryption, pub disabled: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] @@ -143,7 +143,7 @@ pub struct MerchantConnectorAccountNew { pub struct MerchantConnectorAccountNew { pub merchant_id: Option, pub connector_type: Option, - pub connector_name: Option, + pub connector_name: Option, pub connector_account_details: Option, pub disabled: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 03facc2eba..a450a30fa7 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -3426,6 +3426,10 @@ pub enum RedirectForm { access_token: String, step_up_url: String, }, + DeutschebankThreeDSChallengeFlow { + acs_url: String, + creq: String, + }, Payme, Braintree { client_token: String, diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index 8eb0a44f5d..468b4441a8 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -10,6 +10,7 @@ pub mod authentication; pub mod authorization; pub mod blocklist; pub mod blocklist_fingerprint; +pub mod callback_mapper; pub mod customers; pub mod dashboard_metadata; pub mod dispute; diff --git a/crates/diesel_models/src/query/address.rs b/crates/diesel_models/src/query/address.rs index 23c3711a8a..21304a6ab9 100644 --- a/crates/diesel_models/src/query/address.rs +++ b/crates/diesel_models/src/query/address.rs @@ -15,10 +15,7 @@ impl AddressNew { } impl Address { - pub async fn find_by_address_id<'a>( - conn: &PgPooledConn, - address_id: &str, - ) -> StorageResult { + pub async fn find_by_address_id(conn: &PgPooledConn, address_id: &str) -> StorageResult { generics::generic_find_by_id::<::Table, _, _>(conn, address_id.to_owned()) .await } @@ -104,7 +101,7 @@ impl Address { .await } - pub async fn find_by_merchant_id_payment_id_address_id<'a>( + pub async fn find_by_merchant_id_payment_id_address_id( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, payment_id: &common_utils::id_type::PaymentId, @@ -133,7 +130,7 @@ impl Address { } } - pub async fn find_optional_by_address_id<'a>( + pub async fn find_optional_by_address_id( conn: &PgPooledConn, address_id: &str, ) -> StorageResult> { diff --git a/crates/diesel_models/src/query/callback_mapper.rs b/crates/diesel_models/src/query/callback_mapper.rs new file mode 100644 index 0000000000..4210330e07 --- /dev/null +++ b/crates/diesel_models/src/query/callback_mapper.rs @@ -0,0 +1,20 @@ +use diesel::{associations::HasTable, ExpressionMethods}; + +use super::generics; +use crate::{ + callback_mapper::CallbackMapper, schema::callback_mapper::dsl, PgPooledConn, StorageResult, +}; + +impl CallbackMapper { + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } + + pub async fn find_by_id(conn: &PgPooledConn, id: &str) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::id.eq(id.to_owned()), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/relay.rs b/crates/diesel_models/src/query/relay.rs index 034446fe6b..217f6fd734 100644 --- a/crates/diesel_models/src/query/relay.rs +++ b/crates/diesel_models/src/query/relay.rs @@ -1,4 +1,4 @@ -use diesel::{associations::HasTable, ExpressionMethods}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; use super::generics; use crate::{ @@ -46,4 +46,18 @@ impl Relay { ) .await } + + pub async fn find_by_profile_id_connector_reference_id( + conn: &PgPooledConn, + profile_id: &common_utils::id_type::ProfileId, + connector_reference_id: &str, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::profile_id + .eq(profile_id.to_owned()) + .and(dsl::connector_reference_id.eq(connector_reference_id.to_owned())), + ) + .await + } } diff --git a/crates/diesel_models/src/query/role.rs b/crates/diesel_models/src/query/role.rs index 6f6a1404ee..2ab58ec238 100644 --- a/crates/diesel_models/src/query/role.rs +++ b/crates/diesel_models/src/query/role.rs @@ -32,14 +32,18 @@ impl Role { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, - dsl::role_id.eq(role_id.to_owned()).and( - dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id - .eq(org_id.to_owned()) - .and(dsl::scope.eq(RoleScope::Organization))), - ), + dsl::role_id + .eq(role_id.to_owned()) + .and(dsl::tenant_id.eq(tenant_id.to_owned())) + .and( + dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id + .eq(org_id.to_owned()) + .and(dsl::scope.eq(RoleScope::Organization))), + ), ) .await } @@ -49,11 +53,13 @@ impl Role { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, dsl::role_id .eq(role_id.to_owned()) + .and(dsl::tenant_id.eq(tenant_id.to_owned())) .and(dsl::org_id.eq(org_id.to_owned())) .and( dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id @@ -64,15 +70,17 @@ impl Role { .await } - pub async fn find_by_role_id_and_org_id( + pub async fn find_by_role_id_org_id_tenant_id( conn: &PgPooledConn, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, dsl::role_id .eq(role_id.to_owned()) + .and(dsl::tenant_id.eq(tenant_id.to_owned())) .and(dsl::org_id.eq(org_id.to_owned())), ) .await @@ -108,12 +116,16 @@ impl Role { conn: &PgPooledConn, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> StorageResult> { - 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))), - ); + let predicate = dsl::tenant_id + .eq(tenant_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))), + ); generics::generic_filter::<::Table, _, _, _>( conn, @@ -127,13 +139,14 @@ impl Role { pub async fn generic_roles_list_for_org( conn: &PgPooledConn, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, entity_type: Option, limit: Option, ) -> StorageResult> { let mut query = ::table() - .filter(dsl::org_id.eq(org_id)) + .filter(dsl::tenant_id.eq(tenant_id).and(dsl::org_id.eq(org_id))) .into_boxed(); if let Some(merchant_id) = merchant_id { diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs index bb07f67182..01fa05e925 100644 --- a/crates/diesel_models/src/query/user_role.rs +++ b/crates/diesel_models/src/query/user_role.rs @@ -294,4 +294,34 @@ impl UserRole { }, } } + + pub async fn list_user_roles_by_user_id_across_tenants( + conn: &PgPooledConn, + user_id: String, + limit: Option, + ) -> StorageResult> { + let mut query = ::table() + .filter(dsl::user_id.eq(user_id)) + .into_boxed(); + 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::( + query.get_results_async(conn), + generics::db_metrics::DatabaseOperation::Filter, + ) + .await + { + Ok(value) => Ok(value), + Err(err) => match err { + DieselError::NotFound => { + Err(report!(err)).change_context(errors::DatabaseError::NotFound) + } + _ => Err(report!(err)).change_context(errors::DatabaseError::Others), + }, + } + } } diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index 758eba7255..b929481d64 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -54,6 +54,8 @@ pub struct Refund { pub connector_refund_data: Option, pub connector_transaction_data: Option, pub split_refunds: Option, + pub unified_code: Option, + pub unified_message: Option, } #[derive( @@ -132,6 +134,8 @@ pub enum RefundUpdate { updated_by: String, connector_refund_id: Option, connector_refund_data: Option, + unified_code: Option, + unified_message: Option, }, ManualUpdate { refund_status: Option, @@ -155,6 +159,8 @@ pub struct RefundUpdateInternal { updated_by: String, modified_at: PrimitiveDateTime, connector_refund_data: Option, + unified_code: Option, + unified_message: Option, } impl RefundUpdateInternal { @@ -171,6 +177,8 @@ impl RefundUpdateInternal { updated_by: self.updated_by, modified_at: self.modified_at, connector_refund_data: self.connector_refund_data, + unified_code: self.unified_code, + unified_message: self.unified_message, ..source } } @@ -199,6 +207,8 @@ impl From for RefundUpdateInternal { refund_reason: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + unified_code: None, + unified_message: None, }, RefundUpdate::MetadataAndReasonUpdate { metadata, @@ -216,6 +226,8 @@ impl From for RefundUpdateInternal { refund_error_code: None, modified_at: common_utils::date_time::now(), connector_refund_data: None, + unified_code: None, + unified_message: None, }, RefundUpdate::StatusUpdate { connector_refund_id, @@ -235,11 +247,15 @@ impl From for RefundUpdateInternal { refund_reason: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + unified_code: None, + unified_message: None, }, RefundUpdate::ErrorUpdate { refund_status, refund_error_message, refund_error_code, + unified_code, + unified_message, updated_by, connector_refund_id, connector_refund_data, @@ -255,6 +271,8 @@ impl From for RefundUpdateInternal { metadata: None, refund_reason: None, modified_at: common_utils::date_time::now(), + unified_code, + unified_message, }, RefundUpdate::ManualUpdate { refund_status, @@ -273,6 +291,8 @@ impl From for RefundUpdateInternal { refund_reason: None, modified_at: common_utils::date_time::now(), connector_refund_data: None, + unified_code: None, + unified_message: None, }, } } @@ -292,6 +312,8 @@ impl RefundUpdate { updated_by, modified_at: _, connector_refund_data, + unified_code, + unified_message, } = self.into(); Refund { connector_refund_id: connector_refund_id.or(source.connector_refund_id), @@ -305,6 +327,8 @@ impl RefundUpdate { updated_by, modified_at: common_utils::date_time::now(), connector_refund_data: connector_refund_data.or(source.connector_refund_data), + unified_code: unified_code.or(source.unified_code), + unified_message: unified_message.or(source.unified_message), ..source } } @@ -392,6 +416,8 @@ mod tests { "merchant_connector_id": null, "charges": null, "connector_transaction_data": null + "unified_code": null, + "unified_message": null, }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/role.rs b/crates/diesel_models/src/role.rs index 8199bd3979..1672880193 100644 --- a/crates/diesel_models/src/role.rs +++ b/crates/diesel_models/src/role.rs @@ -19,6 +19,7 @@ pub struct Role { pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, pub entity_type: enums::EntityType, + pub tenant_id: id_type::TenantId, } #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -36,6 +37,7 @@ pub struct RoleNew { pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, pub entity_type: enums::EntityType, + pub tenant_id: id_type::TenantId, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 37a39cb731..7930ad5d9a 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -219,6 +219,22 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + callback_mapper (id, type_) { + #[max_length = 128] + id -> Varchar, + #[sql_name = "type"] + #[max_length = 64] + type_ -> Varchar, + data -> Jsonb, + created_at -> Timestamp, + last_modified_at -> Timestamp, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -419,6 +435,8 @@ diesel::table! { created_at -> Timestamp, #[max_length = 64] payment_method_type -> Nullable, + #[max_length = 64] + global_success_based_connector -> Nullable, } } @@ -1235,6 +1253,10 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, split_refunds -> Nullable, + #[max_length = 255] + unified_code -> Nullable, + #[max_length = 1024] + unified_message -> Nullable, } } @@ -1308,6 +1330,8 @@ diesel::table! { last_modified_by -> Varchar, #[max_length = 64] entity_type -> Varchar, + #[max_length = 64] + tenant_id -> Varchar, } } @@ -1486,6 +1510,7 @@ diesel::allow_tables_to_appear_in_same_query!( blocklist_fingerprint, blocklist_lookup, business_profile, + callback_mapper, captures, cards_info, configs, diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 3e27f29427..13abf8ee64 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -227,6 +227,22 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + callback_mapper (id, type_) { + #[max_length = 128] + id -> Varchar, + #[sql_name = "type"] + #[max_length = 64] + type_ -> Varchar, + data -> Jsonb, + created_at -> Timestamp, + last_modified_at -> Timestamp, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -431,6 +447,8 @@ diesel::table! { created_at -> Timestamp, #[max_length = 64] payment_method_type -> Nullable, + #[max_length = 64] + global_success_based_connector -> Nullable, } } @@ -1181,6 +1199,10 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, split_refunds -> Nullable, + #[max_length = 255] + unified_code -> Nullable, + #[max_length = 1024] + unified_message -> Nullable, } } @@ -1255,6 +1277,8 @@ diesel::table! { last_modified_by -> Varchar, #[max_length = 64] entity_type -> Varchar, + #[max_length = 64] + tenant_id -> Varchar, } } @@ -1434,6 +1458,7 @@ diesel::allow_tables_to_appear_in_same_query!( blocklist_fingerprint, blocklist_lookup, business_profile, + callback_mapper, captures, cards_info, configs, diff --git a/crates/euclid/src/backend/vir_interpreter/types.rs b/crates/euclid/src/backend/vir_interpreter/types.rs index d0eca5fec2..c97f60ee17 100644 --- a/crates/euclid/src/backend/vir_interpreter/types.rs +++ b/crates/euclid/src/backend/vir_interpreter/types.rs @@ -31,8 +31,7 @@ impl Context { .get(&key) .and_then(|value| value.get_num_value()); - value.get_num_value().zip(ctx_num_value).map_or( - false, + value.get_num_value().zip(ctx_num_value).is_some_and( |(program_value, ctx_value)| { let program_num = program_value.number; let ctx_num = ctx_value.number; diff --git a/crates/euclid/src/dssa/graph.rs b/crates/euclid/src/dssa/graph.rs index 7ef9bb244d..c4c8c26086 100644 --- a/crates/euclid/src/dssa/graph.rs +++ b/crates/euclid/src/dssa/graph.rs @@ -194,11 +194,11 @@ impl cgraph::CheckingContext for AnalysisContext { DataType::EnumVariant | DataType::StrValue | DataType::MetadataValue => { value_set.contains(val) } - DataType::Number => val.get_num_value().map_or(false, |num_val| { + DataType::Number => val.get_num_value().is_some_and(|num_val| { value_set.iter().any(|ctx_val| { ctx_val .get_num_value() - .map_or(false, |ctx_num_val| num_val.fits(&ctx_num_val)) + .is_some_and(|ctx_num_val| num_val.fits(&ctx_num_val)) }) }), } diff --git a/crates/euclid_wasm/Cargo.toml b/crates/euclid_wasm/Cargo.toml index 19c425e4c0..4f967df34a 100644 --- a/crates/euclid_wasm/Cargo.toml +++ b/crates/euclid_wasm/Cargo.toml @@ -35,7 +35,7 @@ ron-parser = "0.1.4" serde = { version = "1.0", features = [] } serde-wasm-bindgen = "0.6.5" strum = { version = "0.26", features = ["derive"] } -wasm-bindgen = { version = "0.2.92" } +wasm-bindgen = { version = "0.2.99" } [lints] workspace = true diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs index 404685025e..7a627d3ab2 100644 --- a/crates/external_services/src/grpc_client.rs +++ b/crates/external_services/src/grpc_client.rs @@ -128,3 +128,13 @@ impl AddHeaders for tonic::Request { }); } } + +#[cfg(feature = "dynamic_routing")] +pub(crate) fn create_grpc_request(message: T, headers: GrpcHeaders) -> tonic::Request { + let mut request = tonic::Request::new(message); + request.add_headers_to_grpc_request(headers); + + logger::info!(dynamic_routing_request=?request); + + request +} 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 index bc5ce49972..17b332448e 100644 --- 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 @@ -9,6 +9,7 @@ pub use elimination_rate::{ LabelWithBucketName, UpdateEliminationBucketRequest, UpdateEliminationBucketResponse, }; use error_stack::ResultExt; +use router_env::{instrument, logger, tracing}; #[allow( missing_docs, unused_qualifications, @@ -21,7 +22,7 @@ pub mod elimination_rate { } use super::{Client, DynamicRoutingError, DynamicRoutingResult}; -use crate::grpc_client::{AddHeaders, GrpcHeaders}; +use crate::grpc_client::{self, GrpcHeaders}; /// The trait Elimination Based Routing would have the functions required to support performance, calculation and invalidation bucket #[async_trait::async_trait] @@ -54,6 +55,7 @@ pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync { #[async_trait::async_trait] impl EliminationBasedRouting for EliminationAnalyserClient { + #[instrument(skip_all)] async fn perform_elimination_routing( &self, id: String, @@ -69,14 +71,15 @@ impl EliminationBasedRouting for EliminationAnalyserClient { 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 request = grpc_client::create_grpc_request( + EliminationRequest { + id, + params, + labels, + config, + }, + headers, + ); let response = self .clone() @@ -87,9 +90,12 @@ impl EliminationBasedRouting for EliminationAnalyserClient { ))? .into_inner(); + logger::info!(dynamic_routing_response=?response); + Ok(response) } + #[instrument(skip_all)] async fn update_elimination_bucket_config( &self, id: String, @@ -110,14 +116,15 @@ impl EliminationBasedRouting for EliminationAnalyserClient { }) .collect::>(); - let mut request = tonic::Request::new(UpdateEliminationBucketRequest { - id, - params, - labels_with_bucket_name, - config, - }); - - request.add_headers_to_grpc_request(headers); + let request = grpc_client::create_grpc_request( + UpdateEliminationBucketRequest { + id, + params, + labels_with_bucket_name, + config, + }, + headers, + ); let response = self .clone() @@ -127,16 +134,19 @@ impl EliminationBasedRouting for EliminationAnalyserClient { "Failed to update the elimination bucket".to_string(), ))? .into_inner(); + + logger::info!(dynamic_routing_response=?response); + Ok(response) } + + #[instrument(skip_all)] 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 request = grpc_client::create_grpc_request(InvalidateBucketRequest { id }, headers); let response = self .clone() @@ -146,6 +156,9 @@ impl EliminationBasedRouting for EliminationAnalyserClient { "Failed to invalidate the elimination bucket".to_string(), ))? .into_inner(); + + logger::info!(dynamic_routing_response=?response); + Ok(response) } } 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 index 3cf06ab63b..278a2b50d1 100644 --- 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 @@ -1,27 +1,31 @@ use api_models::routing::{ CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, - SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, + SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, SuccessRateSpecificityLevel, }; use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; use error_stack::ResultExt; +use router_env::{instrument, logger, tracing}; pub use success_rate::{ - success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, + success_rate_calculator_client::SuccessRateCalculatorClient, CalGlobalSuccessRateConfig, + CalGlobalSuccessRateRequest, CalGlobalSuccessRateResponse, CalSuccessRateConfig, CalSuccessRateRequest, CalSuccessRateResponse, CurrentBlockThreshold as DynamicCurrentThreshold, InvalidateWindowsRequest, - InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig, + InvalidateWindowsResponse, LabelWithStatus, + SuccessRateSpecificityLevel as ProtoSpecificityLevel, UpdateSuccessRateWindowConfig, UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, }; #[allow( missing_docs, unused_qualifications, clippy::unwrap_used, - clippy::as_conversions + clippy::as_conversions, + clippy::use_self )] pub mod success_rate { tonic::include_proto!("success_rate"); } use super::{Client, DynamicRoutingError, DynamicRoutingResult}; -use crate::grpc_client::{AddHeaders, GrpcHeaders}; +use crate::grpc_client::{self, 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 { @@ -49,10 +53,20 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { id: String, headers: GrpcHeaders, ) -> DynamicRoutingResult; + /// To calculate both global and merchant specific success rate for the list of chosen connectors + async fn calculate_entity_and_global_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; } #[async_trait::async_trait] impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { + #[instrument(skip_all)] async fn calculate_success_rate( &self, id: String, @@ -71,14 +85,15 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { .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 request = grpc_client::create_grpc_request( + CalSuccessRateRequest { + id, + params, + labels, + config, + }, + headers, + ); let response = self .clone() @@ -89,9 +104,12 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { ))? .into_inner(); + logger::info!(dynamic_routing_response=?response); + Ok(response) } + #[instrument(skip_all)] async fn update_success_rate( &self, id: String, @@ -106,6 +124,7 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { .transpose()?; let labels_with_status = label_input + .clone() .into_iter() .map(|conn_choice| LabelWithStatus { label: conn_choice.routable_connector_choice.to_string(), @@ -113,14 +132,24 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { }) .collect(); - let mut request = tonic::Request::new(UpdateSuccessRateWindowRequest { - id, - params, - labels_with_status, - config, - }); + let global_labels_with_status = label_input + .into_iter() + .map(|conn_choice| LabelWithStatus { + label: conn_choice.routable_connector_choice.connector.to_string(), + status: conn_choice.status, + }) + .collect(); - request.add_headers_to_grpc_request(headers); + let request = grpc_client::create_grpc_request( + UpdateSuccessRateWindowRequest { + id, + params, + labels_with_status, + config, + global_labels_with_status, + }, + headers, + ); let response = self .clone() @@ -131,16 +160,18 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { ))? .into_inner(); + logger::info!(dynamic_routing_response=?response); + Ok(response) } + + #[instrument(skip_all)] 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 request = grpc_client::create_grpc_request(InvalidateWindowsRequest { id }, headers); let response = self .clone() @@ -150,6 +181,58 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { "Failed to invalidate the success rate routing keys".to_string(), ))? .into_inner(); + + logger::info!(dynamic_routing_response=?response); + + Ok(response) + } + + async fn calculate_entity_and_global_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let labels = label_input + .clone() + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let global_labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.connector.to_string()) + .collect::>(); + + let config = success_rate_based_config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let request = grpc_client::create_grpc_request( + CalGlobalSuccessRateRequest { + entity_id: id, + entity_params: params, + entity_labels: labels, + global_labels, + config, + }, + headers, + ); + + let response = self + .clone() + .fetch_entity_and_global_success_rate(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to fetch the entity and global success rate".to_string(), + ))? + .into_inner(); + + logger::info!(dynamic_routing_response=?response); + Ok(response) } } @@ -203,6 +286,30 @@ impl ForeignTryFrom for CalSuccessRateConfig { .change_context(DynamicRoutingError::MissingRequiredField { field: "default_success_rate".to_string(), })?, + specificity_level: match config.specificity_level { + SuccessRateSpecificityLevel::Merchant => Some(ProtoSpecificityLevel::Entity.into()), + SuccessRateSpecificityLevel::Global => Some(ProtoSpecificityLevel::Global.into()), + }, + }) + } +} + +impl ForeignTryFrom for CalGlobalSuccessRateConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { + Ok(Self { + entity_min_aggregates_size: config + .min_aggregates_size + .get_required_value("min_aggregate_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "min_aggregates_size".to_string(), + })?, + entity_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/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index ca9e77283c..68ea60af8c 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -21,6 +21,7 @@ error-stack = "0.4.1" hex = "0.4.3" http = "0.2.12" image = { version = "0.25.1", default-features = false, features = ["png"] } +josekit = "0.8.6" mime = "0.3.17" once_cell = "1.19.0" qrcode = "0.14.0" diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index d5a695fe01..bd646c95eb 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -2,6 +2,7 @@ pub mod airwallex; pub mod amazonpay; pub mod bambora; pub mod bamboraapac; +pub mod bankofamerica; pub mod billwerk; pub mod bitpay; pub mod bluesnap; @@ -10,6 +11,7 @@ pub mod cashtocode; pub mod coinbase; pub mod cryptopay; pub mod ctp_mastercard; +pub mod cybersource; pub mod datatrans; pub mod deutschebank; pub mod digitalvirgo; @@ -47,6 +49,7 @@ pub mod thunes; pub mod tsys; pub mod unified_authentication_service; pub mod volt; +pub mod wellsfargo; pub mod worldline; pub mod worldpay; pub mod xendit; @@ -55,8 +58,9 @@ pub mod zsl; pub use self::{ 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, + bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, + boku::Boku, cashtocode::Cashtocode, coinbase::Coinbase, cryptopay::Cryptopay, + ctp_mastercard::CtpMastercard, cybersource::Cybersource, 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, @@ -65,5 +69,6 @@ pub use self::{ 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, + wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, + zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index 075a7b070d..45a7b577a8 100644 --- a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -147,12 +147,6 @@ pub struct Browser { user_agent: String, } -#[derive(Debug, Serialize)] -pub struct Location { - lat: String, - lon: String, -} - #[derive(Debug, Serialize)] pub struct Mobile { device_model: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/bambora.rs b/crates/hyperswitch_connectors/src/connectors/bambora.rs index 193a16df0b..d972e004f2 100644 --- a/crates/hyperswitch_connectors/src/connectors/bambora.rs +++ b/crates/hyperswitch_connectors/src/connectors/bambora.rs @@ -825,6 +825,15 @@ lazy_static! { enums::CaptureMethod::SequentialAutomatic, ]; + let supported_card_network = vec![ + common_enums::CardNetwork::Visa, + common_enums::CardNetwork::Mastercard, + common_enums::CardNetwork::AmericanExpress, + common_enums::CardNetwork::Discover, + common_enums::CardNetwork::JCB, + common_enums::CardNetwork::DinersClub, + ]; + let mut bambora_supported_payment_methods = SupportedPaymentMethods::new(); bambora_supported_payment_methods.add( @@ -834,15 +843,15 @@ lazy_static! { 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(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::Supported, + non_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), }, ); diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica.rs similarity index 70% rename from crates/router/src/connector/bankofamerica.rs rename to crates/hyperswitch_connectors/src/connectors/bankofamerica.rs index 95ceee5558..36d12fe3fc 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica.rs @@ -3,36 +3,57 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::request::RequestContent; -use diesel_models::enums; +use common_enums::enums; +use common_utils::{ + consts, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, +}; use error_stack::{report, ResultExt}; -use masking::{ExposeInterface, PeekInterface}; +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, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, + RefundExecuteType, RefundSyncType, Response, SetupMandateType, + }, + webhooks, +}; +use masking::{ExposeInterface, Mask, Maskable, PeekInterface}; use ring::{digest, hmac}; use time::OffsetDateTime; use transformers as bankofamerica; use url::Url; use crate::{ - configs::settings, - connector::{ - utils as connector_utils, - utils::{PaymentMethodDataType, RefundsRequestData}, - }, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, - }, - utils::BytesExt, + constants::{self, headers}, + types::ResponseRouterData, + utils::{self, PaymentMethodDataType, RefundsRequestData}, }; pub const V_C_MERCHANT_ID: &str = "v-c-merchant-id"; @@ -66,14 +87,14 @@ impl Bankofamerica { resource: &str, payload: &String, date: OffsetDateTime, - http_method: services::Method, + http_method: Method, ) -> CustomResult { let bankofamerica::BankOfAmericaAuthType { api_key, merchant_account, api_secret, } = auth; - let is_post_method = matches!(http_method, services::Method::Post); + let is_post_method = matches!(http_method, Method::Post); let digest_str = if is_post_method { "digest " } else { "" }; let headers = format!("host date (request-target) {digest_str}{V_C_MERCHANT_ID}"); let request_target = if is_post_method { @@ -102,12 +123,8 @@ impl Bankofamerica { } } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Bankofamerica +impl ConnectorIntegration + for Bankofamerica { // Not Implemented (R) } @@ -118,9 +135,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let date = OffsetDateTime::now_utc(); let boa_req = self.get_request_body(req, connectors)?; let http_method = self.get_http_method(); @@ -161,7 +178,7 @@ where ("Host".to_string(), host.to_string().into()), ("Signature".to_string(), signature.into_masked()), ]; - if matches!(http_method, services::Method::Post | services::Method::Put) { + if matches!(http_method, Method::Post | Method::Put) { headers.push(( "Digest".to_string(), format!("SHA-256={sha256}").into_masked(), @@ -184,7 +201,7 @@ impl ConnectorCommon for Bankofamerica { "application/json;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.bankofamerica.base_url.as_ref() } @@ -202,9 +219,9 @@ impl ConnectorCommon for Bankofamerica { router_env::logger::info!(connector_response=?response); let error_message = if res.status_code == 401 { - consts::CONNECTOR_UNAUTHORIZED_ERROR + constants::CONNECTOR_UNAUTHORIZED_ERROR } else { - consts::NO_ERROR_MESSAGE + hyperswitch_interfaces::consts::NO_ERROR_MESSAGE }; match response { transformers::BankOfAmericaErrorResponse::StandardError(response) => { @@ -236,12 +253,10 @@ impl ConnectorCommon for Bankofamerica { .join(", ") }); ( - response - .reason - .clone() - .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { - reason.to_string() - }), + response.reason.clone().map_or( + hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), + |reason| reason.to_string(), + ), response .reason .map_or(error_message.to_string(), |reason| reason.to_string()), @@ -266,7 +281,7 @@ impl ConnectorCommon for Bankofamerica { transformers::BankOfAmericaErrorResponse::AuthenticationError(response) => { Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: response.response.rmsg.clone(), reason: Some(response.response.rmsg), attempt_status: None, @@ -290,48 +305,39 @@ impl ConnectorValidation for Bankofamerica { | 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()), + utils::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::Card, PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } -impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { //TODO: implement sessions flow } -impl ConnectorIntegration - for Bankofamerica -{ -} +impl ConnectorIntegration for Bankofamerica {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Bankofamerica +impl ConnectorIntegration + for Bankofamerica { 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_content_type(&self) -> &'static str { @@ -339,15 +345,15 @@ impl } fn get_url( &self, - _req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &SetupMandateRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, + req: &SetupMandateRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = bankofamerica::BankOfAmericaPaymentsRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -355,31 +361,25 @@ impl 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> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::SetupMandateType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&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, - )?) + .headers(SetupMandateType::get_headers(self, req, connectors)?) + .set_body(SetupMandateType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &SetupMandateRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res .response .parse_struct("BankOfAmericaSetupMandatesResponse") @@ -388,7 +388,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, @@ -426,24 +426,26 @@ impl Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl ConnectorIntegration +impl ConnectorIntegration for Bankofamerica { 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) } @@ -453,8 +455,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}pts/v2/payments/", @@ -464,8 +466,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), @@ -480,20 +482,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(), @@ -502,17 +500,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bankofamerica::BankOfAmericaPaymentsResponse = res .response .parse_struct("Bankofamerica 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, @@ -550,24 +548,24 @@ impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { 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) } @@ -575,14 +573,14 @@ impl ConnectorIntegration services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req .request @@ -597,32 +595,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: bankofamerica::BankOfAmericaTransactionResponse = res .response .parse_struct("BankOfAmerica PaymentSyncResponse") .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, @@ -638,14 +636,12 @@ impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { 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) } @@ -655,8 +651,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -667,8 +663,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), @@ -683,18 +679,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(), @@ -703,17 +697,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bankofamerica::BankOfAmericaPaymentsResponse = res .response .parse_struct("BankOfAmerica 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, @@ -744,31 +738,31 @@ impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { 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) } fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -783,8 +777,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), @@ -808,35 +802,33 @@ 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)?) + RequestBuilder::new() + .method(Method::Post) + .url(&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, - )?) + .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: bankofamerica::BankOfAmericaPaymentsResponse = res .response .parse_struct("BankOfAmerica 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, @@ -867,24 +859,24 @@ impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { 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 +886,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -906,8 +898,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), @@ -922,29 +914,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: bankofamerica::BankOfAmericaRefundResponse = res .response .parse_struct("bankofamerica RefundResponse") @@ -953,7 +941,7 @@ impl ConnectorIntegration - for Bankofamerica -{ +impl ConnectorIntegration for Bankofamerica { 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) } @@ -984,14 +970,14 @@ impl ConnectorIntegration services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -1002,32 +988,32 @@ 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: bankofamerica::BankOfAmericaRsyncResponse = res .response .parse_struct("bankofamerica 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, @@ -1044,24 +1030,24 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs similarity index 70% rename from crates/router/src/connector/bankofamerica/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index 71cc006700..3602f77192 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -1,38 +1,48 @@ use base64::Engine; -use common_utils::pii; +use common_enums::{enums, FutureUsage}; +use common_utils::{consts, pii}; +use hyperswitch_domain_models::{ + payment_method_data::{ApplePayWalletData, GooglePayWalletData, PaymentMethodData, WalletData}, + router_data::{ + AdditionalPaymentMethodConnectorResponse, ApplePayPredecryptData, ConnectorAuthType, + ConnectorResponseData, ErrorResponse, PaymentMethodToken, RouterData, + }, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, + ResponseId, + }, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{api, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::{ - connector::utils::{ - self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, - PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData, - RecurringMandateData, RouterData, - }, - consts, - core::errors, - types::{ - self, - api::{self, enums as api_enums}, - domain, - storage::enums, - transformers::ForeignFrom, - ApplePayPredecryptData, - }, + constants, + types::{RefundsResponseRouterData, ResponseRouterData}, unimplemented_payment_method, + utils::{ + self, AddressDetailsData, ApplePayDecrypt, CardData, PaymentsAuthorizeRequestData, + PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RecurringMandateData, + RouterData as OtherRouterData, + }, }; - pub struct BankOfAmericaAuthType { pub(super) api_key: Secret, pub(super) merchant_account: Secret, pub(super) api_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BankOfAmericaAuthType { +impl TryFrom<&ConnectorAuthType> for BankOfAmericaAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -54,10 +64,17 @@ pub struct BankOfAmericaRouterData { pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for BankOfAmericaRouterData { +impl TryFrom<(&api::CurrencyUnit, api_models::enums::Currency, i64, T)> + for BankOfAmericaRouterData +{ type Error = error_stack::Report; fn try_from( - (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), + (currency_unit, currency, amount, item): ( + &api::CurrencyUnit, + api_models::enums::Currency, + i64, + T, + ), ) -> Result { let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; Ok(Self { @@ -264,68 +281,64 @@ pub struct BillTo { administrative_area: Option>, #[serde(skip_serializing_if = "Option::is_none")] postal_code: Option>, - country: Option, + country: Option, email: pii::Email, } -impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { +impl TryFrom<&SetupMandateRouterData> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::SetupMandateRouterData) -> Result { + fn try_from(item: &SetupMandateRouterData) -> Result { match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { - Self::try_from((item, apple_pay_data)) - } - domain::WalletData::GooglePay(google_pay_data) => { - Self::try_from((item, google_pay_data)) - } - 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::Paze(_) - | 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(card_data) => Self::try_from((item, card_data)), + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => Self::try_from((item, apple_pay_data)), + WalletData::GooglePay(google_pay_data) => Self::try_from((item, google_pay_data)), + 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("BankOfAmerica"), ))?, }, - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + 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("BankOfAmerica"), ))? @@ -335,38 +348,29 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { } impl - TryFrom< - types::ResponseRouterData< - F, - BankOfAmericaSetupMandatesResponse, - T, - types::PaymentsResponseData, - >, - > for types::RouterData + TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - BankOfAmericaSetupMandatesResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { match item.response { BankOfAmericaSetupMandatesResponse::ClientReferenceInformation(info_response) => { - let mandate_reference = info_response.token_information.clone().map(|token_info| { - types::MandateReference { - connector_mandate_id: token_info - .payment_instrument - .map(|payment_instrument| payment_instrument.id.expose()), - payment_method_id: None, - mandate_metadata: None, - connector_mandate_request_reference_id: None, - } - }); + let mandate_reference = + info_response + .token_information + .clone() + .map(|token_info| MandateReference { + connector_mandate_id: token_info + .payment_instrument + .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((info_response.status.clone(), false)); + map_boa_attempt_status((info_response.status.clone(), false)); if matches!(mandate_status, enums::AttemptStatus::Authorized) { //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. mandate_status = enums::AttemptStatus::Charged @@ -383,13 +387,13 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::foreign_from(( + convert_to_additional_payment_method_connector_response( processor_information, consumer_auth_information, - )) + ) }) }) - .map(types::ConnectorResponseData::with_additional_payment_method_data), + .map(ConnectorResponseData::with_additional_payment_method_data), common_enums::PaymentMethod::CardRedirect | common_enums::PaymentMethod::PayLater | common_enums::PaymentMethod::Wallet @@ -410,8 +414,8 @@ impl status: mandate_status, response: match error_response { Some(error) => Err(error), - None => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + None => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( info_response.id.clone(), ), redirection_data: Box::new(None), @@ -434,10 +438,10 @@ impl }) } BankOfAmericaSetupMandatesResponse::ErrorInformation(error_response) => { - let response = Err(types::ErrorResponse::foreign_from(( - &*error_response, + let response = Err(convert_to_error_response_from_error_info( + &error_response, item.http_code, - ))); + )); Ok(Self { response, status: enums::AttemptStatus::Failure, @@ -522,23 +526,6 @@ fn build_bill_to( .unwrap_or(default_address)) } -impl From for String { - fn from(card_issuer: CardIssuer) -> Self { - let card_type = match card_issuer { - CardIssuer::AmericanExpress => "003", - CardIssuer::Master => "002", - //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" - CardIssuer::Maestro => "042", - CardIssuer::Visa => "001", - CardIssuer::Discover => "004", - CardIssuer::DinersClub => "005", - CardIssuer::CarteBlanche => "006", - CardIssuer::JCB => "007", - }; - card_type.to_string() - } -} - fn get_boa_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { match card_network { common_enums::CardNetwork::Visa => Some("001"), @@ -579,13 +566,13 @@ pub enum TransactionType { impl From<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Option, )> for OrderInformationWithBill { fn from( (item, bill_to): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Option, ), ) -> Self { @@ -601,7 +588,7 @@ impl impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Option, Option, )> for ProcessingInformation @@ -610,55 +597,51 @@ impl fn try_from( (item, solution, network): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Option, Option, ), ) -> Result { - let (action_list, action_token_types, authorization_options) = if item - .router_data - .request - .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, common_enums::FutureUsage::OffSession) - }) - && (item.router_data.request.customer_acceptance.is_some() - || item + let (action_list, action_token_types, authorization_options) = + if item.router_data.request.setup_future_usage == Some(FutureUsage::OffSession) + && (item.router_data.request.customer_acceptance.is_some() + || item + .router_data + .request + .setup_mandate_details + .clone() + .is_some_and(|mandate_details| { + mandate_details.customer_acceptance.is_some() + })) + { + get_boa_mandate_action_details() + } else if item.router_data.request.connector_mandate_id().is_some() { + let original_amount = item .router_data - .request - .setup_mandate_details - .clone() - .map_or(false, |mandate_details| { - mandate_details.customer_acceptance.is_some() - })) { - get_boa_mandate_action_details() - } else if item.router_data.request.connector_mandate_id().is_some() { - let original_amount = item - .router_data - .get_recurring_mandate_payment_data()? - .get_original_payment_amount()?; - let original_currency = item - .router_data - .get_recurring_mandate_payment_data()? - .get_original_payment_currency()?; - ( - None, - None, - Some(BankOfAmericaAuthorizationOptions { - initiator: None, - merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { - reason: None, - original_authorized_amount: Some(utils::get_amount_as_string( - &api::CurrencyUnit::Base, - original_amount, - original_currency, - )?), + .get_recurring_mandate_payment_data()? + .get_original_payment_amount()?; + let original_currency = item + .router_data + .get_recurring_mandate_payment_data()? + .get_original_payment_currency()?; + ( + None, + None, + Some(BankOfAmericaAuthorizationOptions { + initiator: None, + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: None, + original_authorized_amount: Some(utils::get_amount_as_string( + &api::CurrencyUnit::Base, + original_amount, + original_currency, + )?), + }), }), - }), - ) - } else { - (None, None, None) - }; + ) + } else { + (None, None, None) + }; let commerce_indicator = get_commerce_indicator(network); @@ -677,40 +660,35 @@ impl } } -impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> - for ClientReferenceInformation -{ - fn from(item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { +impl From<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> for ClientReferenceInformation { + fn from(item: &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>) -> Self { Self { code: Some(item.router_data.connector_request_reference_id.clone()), } } } -impl From<&types::SetupMandateRouterData> for ClientReferenceInformation { - fn from(item: &types::SetupMandateRouterData) -> Self { +impl From<&SetupMandateRouterData> for ClientReferenceInformation { + fn from(item: &SetupMandateRouterData) -> Self { Self { code: Some(item.connector_request_reference_id.clone()), } } } -impl ForeignFrom for Vec { - fn foreign_from(metadata: Value) -> Self { - let hashmap: std::collections::BTreeMap = - serde_json::from_str(&metadata.to_string()) - .unwrap_or(std::collections::BTreeMap::new()); - let mut vector: Self = Self::new(); - let mut iter = 1; - for (key, value) in hashmap { - vector.push(MerchantDefinedInformation { - key: iter, - value: format!("{key}={value}"), - }); - iter += 1; - } - vector +fn convert_metadata_to_merchant_defined_info(metadata: Value) -> Vec { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()).unwrap_or(std::collections::BTreeMap::new()); + let mut vector = Vec::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; } + vector } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -824,15 +802,15 @@ pub struct Avs { impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, )> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -846,7 +824,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -861,17 +839,17 @@ impl impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, )> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, apple_pay_data, apple_pay_wallet_data): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -889,7 +867,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let ucaf_collection_indicator = match apple_pay_wallet_data .payment_method .network @@ -919,15 +897,15 @@ impl impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, )> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, google_pay_data): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -942,7 +920,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -955,33 +933,33 @@ impl } } -impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> +impl TryFrom<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + item: &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.connector_mandate_id() { Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), None => { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { + PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => { match item.router_data.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { Self::try_from((item, decrypt_data, apple_pay_data)) } - types::PaymentMethodToken::Token(_) => { + PaymentMethodToken::Token(_) => { Err(unimplemented_payment_method!( "Apple Pay", "Manual", "Bank Of America" ))? } - types::PaymentMethodToken::PazeDecrypt(_) => Err( + PaymentMethodToken::PazeDecrypt(_) => Err( unimplemented_payment_method!("Paze", "Bank Of America"), )?, }, @@ -1003,12 +981,12 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> ClientReferenceInformation::from(item); let payment_information = PaymentInformation::from(&apple_pay_data); - let merchant_defined_information = - item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from( - metadata, - ) - }); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(convert_metadata_to_merchant_defined_info); let ucaf_collection_indicator = match apple_pay_data .payment_method .network @@ -1038,47 +1016,45 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } } - domain::WalletData::GooglePay(google_pay_data) => { + WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } - 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::Paze(_) - | 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( - utils::get_unimplemented_payment_method_error_message( - "Bank of America", - ), - ) - .into()) - } + 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( + "Bank of America", + ), + ) + .into()), }, // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. // This is a fallback implementation in the event of catastrophe. - domain::PaymentMethodData::MandatePayment => { + PaymentMethodData::MandatePayment => { let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or( errors::ConnectorError::MissingRequiredField { @@ -1087,22 +1063,22 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> )?; Self::try_from((item, connector_mandate_id)) } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | 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::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | 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( "Bank of America", @@ -1118,14 +1094,14 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, String, )> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, connector_mandate_id): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>, String, ), ) -> Result { @@ -1148,7 +1124,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, payment_information, @@ -1184,42 +1160,44 @@ pub enum BankofamericaPaymentStatus { //PartialAuthorized, not being consumed yet. } -impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { - fn foreign_from((status, auto_capture): (BankofamericaPaymentStatus, bool)) -> Self { - match status { - BankofamericaPaymentStatus::Authorized - | BankofamericaPaymentStatus::AuthorizedPendingReview => { - if auto_capture { - // Because BankOfAmerica will return Payment Status as Authorized even in AutoCapture Payment - Self::Charged - } else { - Self::Authorized - } - } - BankofamericaPaymentStatus::Pending => { - if auto_capture { - Self::Charged - } else { - Self::Pending - } +fn map_boa_attempt_status( + (status, auto_capture): (BankofamericaPaymentStatus, bool), +) -> enums::AttemptStatus { + match status { + BankofamericaPaymentStatus::Authorized + | BankofamericaPaymentStatus::AuthorizedPendingReview => { + if auto_capture { + // Because BankOfAmerica will return Payment Status as Authorized even in AutoCapture Payment + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } - BankofamericaPaymentStatus::Succeeded | BankofamericaPaymentStatus::Transmitted => { - Self::Charged + } + BankofamericaPaymentStatus::Pending => { + if auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Pending } - BankofamericaPaymentStatus::Voided - | BankofamericaPaymentStatus::Reversed - | BankofamericaPaymentStatus::Cancelled => Self::Voided, - BankofamericaPaymentStatus::Failed - | BankofamericaPaymentStatus::Declined - | BankofamericaPaymentStatus::AuthorizedRiskDeclined - | BankofamericaPaymentStatus::InvalidRequest - | BankofamericaPaymentStatus::Rejected - | BankofamericaPaymentStatus::ServerError => Self::Failure, - BankofamericaPaymentStatus::PendingAuthentication => Self::AuthenticationPending, - BankofamericaPaymentStatus::PendingReview - | BankofamericaPaymentStatus::Challenge - | BankofamericaPaymentStatus::Accepted => Self::Pending, } + BankofamericaPaymentStatus::Succeeded | BankofamericaPaymentStatus::Transmitted => { + enums::AttemptStatus::Charged + } + BankofamericaPaymentStatus::Voided + | BankofamericaPaymentStatus::Reversed + | BankofamericaPaymentStatus::Cancelled => enums::AttemptStatus::Voided, + BankofamericaPaymentStatus::Failed + | BankofamericaPaymentStatus::Declined + | BankofamericaPaymentStatus::AuthorizedRiskDeclined + | BankofamericaPaymentStatus::InvalidRequest + | BankofamericaPaymentStatus::Rejected + | BankofamericaPaymentStatus::ServerError => enums::AttemptStatus::Failure, + BankofamericaPaymentStatus::PendingAuthentication => { + enums::AttemptStatus::AuthenticationPending + } + BankofamericaPaymentStatus::PendingReview + | BankofamericaPaymentStatus::Challenge + | BankofamericaPaymentStatus::Accepted => enums::AttemptStatus::Pending, } } @@ -1303,7 +1281,7 @@ pub struct PaymentInformationResponse { bin: Option, account_type: Option, issuer: Option, - bin_country: Option, + bin_country: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1374,7 +1352,7 @@ pub struct BankOfAmericaTokenInformation { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IssuerInformation { - country: Option, + country: Option, discretionary_data: Option, country_specific_discretionary_data: Option, response_code: Option, @@ -1406,71 +1384,55 @@ pub struct BankOfAmericaErrorInformation { details: Option>, } -impl - ForeignFrom<( - &BankOfAmericaErrorInformationResponse, - types::ResponseRouterData, - Option, - )> for types::RouterData -{ - fn foreign_from( - (error_response, item, transaction_status): ( - &BankOfAmericaErrorInformationResponse, - types::ResponseRouterData< - F, - BankOfAmericaPaymentsResponse, - T, - types::PaymentsResponseData, - >, - Option, - ), - ) -> Self { - let detailed_error_info = - error_response - .error_information - .details - .as_ref() - .map(|details| { - details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", ") - }); - - let reason = get_error_reason( - error_response.error_information.message.clone(), - detailed_error_info, - None, - ); - let response = Err(types::ErrorResponse { - code: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), +fn map_error_response( + error_response: &BankOfAmericaErrorInformationResponse, + item: ResponseRouterData, + transaction_status: Option, +) -> RouterData { + let detailed_error_info = error_response + .error_information + .details + .as_ref() + .map(|details| { + details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") }); - match transaction_status { - Some(status) => Self { - response, - status, - ..item.data - }, - None => Self { - response, - ..item.data - }, - } + let reason = get_error_reason( + error_response.error_information.message.clone(), + detailed_error_info, + None, + ); + let response = Err(ErrorResponse { + code: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + + match transaction_status { + Some(status) => RouterData { + response, + status, + ..item.data + }, + None => RouterData { + response, + ..item.data + }, } } @@ -1480,15 +1442,15 @@ fn get_error_response_if_failure( enums::AttemptStatus, u16, ), -) -> Option { +) -> Option { if utils::is_payment_failure(status) { - Some(types::ErrorResponse::foreign_from(( + Some(get_error_response( &info_response.error_information, &info_response.risk_information, Some(status), http_code, info_response.id.clone(), - ))) + )) } else { None } @@ -1500,7 +1462,7 @@ fn get_payment_response( enums::AttemptStatus, u16, ), -) -> Result { +) -> Result { let error_response = get_error_response_if_failure((info_response, status, http_code)); match error_response { Some(error) => Err(error), @@ -1509,7 +1471,7 @@ fn get_payment_response( info_response .token_information .clone() - .map(|token_info| types::MandateReference { + .map(|token_info| MandateReference { connector_mandate_id: token_info .payment_instrument .map(|payment_instrument| payment_instrument.id.expose()), @@ -1518,8 +1480,8 @@ fn get_payment_response( connector_mandate_request_reference_id: None, }); - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(info_response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -1540,26 +1502,26 @@ fn get_payment_response( impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BankOfAmericaPaymentsResponse, - 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, BankOfAmericaPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { - let status = enums::AttemptStatus::foreign_from(( + let status = map_boa_attempt_status(( info_response.status.clone(), item.data.request.is_auto_capture()?, )); @@ -1573,13 +1535,13 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::foreign_from(( + convert_to_additional_payment_method_connector_response( processor_information, consumer_auth_information, - )) + ) }) }) - .map(types::ConnectorResponseData::with_additional_payment_method_data), + .map(ConnectorResponseData::with_additional_payment_method_data), common_enums::PaymentMethod::CardRedirect | common_enums::PaymentMethod::PayLater | common_enums::PaymentMethod::Wallet @@ -1604,31 +1566,21 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::foreign_from(( - &*error_response.clone(), + Ok(map_error_response( + &error_response.clone(), item, Some(enums::AttemptStatus::Failure), - ))) + )) } } } } -impl - ForeignFrom<( - &ClientProcessorInformation, - &ConsumerAuthenticationInformation, - )> for types::AdditionalPaymentMethodConnectorResponse -{ - fn foreign_from( - item: ( - &ClientProcessorInformation, - &ConsumerAuthenticationInformation, - ), - ) -> Self { - let processor_information = item.0; - let consumer_authentication_information = item.1; - let payment_checks = Some(serde_json::json!({ +fn convert_to_additional_payment_method_connector_response( + processor_information: &ClientProcessorInformation, + consumer_authentication_information: &ConsumerAuthenticationInformation, +) -> AdditionalPaymentMethodConnectorResponse { + let payment_checks = Some(serde_json::json!({ "avs_response": processor_information.avs, "card_verification": processor_information.card_verification, "approval_code": processor_information.approval_code, @@ -1636,44 +1588,42 @@ impl "cavv": consumer_authentication_information.cavv, "eci": consumer_authentication_information.eci, "eci_raw": consumer_authentication_information.eci_raw, - })); + })); - let authentication_data = Some(serde_json::json!({ + let authentication_data = Some(serde_json::json!({ "retrieval_reference_number": processor_information.retrieval_reference_number, "acs_transaction_id": consumer_authentication_information.acs_transaction_id, "system_trace_audit_number": processor_information.system_trace_audit_number, - })); + })); - Self::Card { - authentication_data, - payment_checks, - } + AdditionalPaymentMethodConnectorResponse::Card { + authentication_data, + payment_checks, } } impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BankOfAmericaPaymentsResponse, - 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, BankOfAmericaPaymentsResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, ) -> Result { match item.response { BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { - let status = - enums::AttemptStatus::foreign_from((info_response.status.clone(), true)); + let status = map_boa_attempt_status((info_response.status.clone(), true)); let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { status, @@ -1682,7 +1632,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::foreign_from((&*error_response.clone(), item, None))) + Ok(map_error_response(&error_response.clone(), item, None)) } } } @@ -1690,27 +1640,26 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BankOfAmericaPaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, + PaymentsCancelData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BankOfAmericaPaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, + PaymentsCancelData, + PaymentsResponseData, >, ) -> Result { match item.response { BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { - let status = - enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + let status = map_boa_attempt_status((info_response.status.clone(), false)); let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { status, @@ -1719,7 +1668,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::foreign_from((&*error_response.clone(), item, None))) + Ok(map_error_response(&error_response.clone(), item, None)) } } } @@ -1757,29 +1706,27 @@ pub struct ApplicationInformation { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BankOfAmericaTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BankOfAmericaTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, ) -> Result { match item.response.application_information.status { Some(app_status) => { - let status = enums::AttemptStatus::foreign_from(( - app_status, - item.data.request.is_auto_capture()?, - )); + let status = + map_boa_attempt_status((app_status, item.data.request.is_auto_capture()?)); let connector_response = match item.data.payment_method { common_enums::PaymentMethod::Card => item @@ -1791,13 +1738,13 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::foreign_from(( + convert_to_additional_payment_method_connector_response( processor_information, consumer_auth_information, - )) + ) }) }) - .map(types::ConnectorResponseData::with_additional_payment_method_data), + .map(ConnectorResponseData::with_additional_payment_method_data), common_enums::PaymentMethod::CardRedirect | common_enums::PaymentMethod::PayLater | common_enums::PaymentMethod::Wallet @@ -1817,13 +1764,13 @@ impl let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { - response: Err(types::ErrorResponse::foreign_from(( + response: Err(get_error_response( &item.response.error_information, &risk_info, Some(status), item.http_code, item.response.id.clone(), - ))), + )), status: enums::AttemptStatus::Failure, connector_response, ..item.data @@ -1831,8 +1778,8 @@ impl } else { Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.id.clone(), ), redirection_data: Box::new(None), @@ -1854,10 +1801,8 @@ impl } None => Ok(Self { status: item.data.status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -1887,19 +1832,17 @@ pub struct BankOfAmericaCaptureRequest { merchant_defined_information: Option>, } -impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>> - for BankOfAmericaCaptureRequest -{ +impl TryFrom<&BankOfAmericaRouterData<&PaymentsCaptureRouterData>> for BankOfAmericaCaptureRequest { type Error = error_stack::Report; fn try_from( - value: &BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>, + value: &BankOfAmericaRouterData<&PaymentsCaptureRouterData>, ) -> Result { let merchant_defined_information = value .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { order_information: OrderInformation { amount_details: Amount { @@ -1932,19 +1875,17 @@ pub struct ReversalInformation { reason: String, } -impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCancelRouterData>> - for BankOfAmericaVoidRequest -{ +impl TryFrom<&BankOfAmericaRouterData<&PaymentsCancelRouterData>> for BankOfAmericaVoidRequest { type Error = error_stack::Report; fn try_from( - value: &BankOfAmericaRouterData<&types::PaymentsCancelRouterData>, + value: &BankOfAmericaRouterData<&PaymentsCancelRouterData>, ) -> Result { let merchant_defined_information = value .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { client_reference_information: ClientReferenceInformation { code: Some(value.router_data.connector_request_reference_id.clone()), @@ -1979,12 +1920,10 @@ pub struct BankOfAmericaRefundRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&BankOfAmericaRouterData<&types::RefundsRouterData>> - for BankOfAmericaRefundRequest -{ +impl TryFrom<&BankOfAmericaRouterData<&RefundsRouterData>> for BankOfAmericaRefundRequest { type Error = error_stack::Report; fn try_from( - item: &BankOfAmericaRouterData<&types::RefundsRouterData>, + item: &BankOfAmericaRouterData<&RefundsRouterData>, ) -> Result { Ok(Self { order_information: OrderInformation { @@ -2032,24 +1971,24 @@ pub struct BankOfAmericaRefundResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.clone()); let response = if utils::is_refund_failure(refund_status) { - Err(types::ErrorResponse::foreign_from(( + Err(get_error_response( &item.response.error_information, &None, None, item.http_code, item.response.id, - ))) + )) } else { - Ok(types::RefundsResponseData { + Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }) @@ -2089,12 +2028,12 @@ pub struct BankOfAmericaRsyncResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let response = match item .response @@ -2124,35 +2063,35 @@ impl TryFrom Ok(types::RefundsResponseData { + None => Ok(RefundsResponseData { connector_refund_id: item.response.id.clone(), refund_status: match item.data.response { Ok(response) => response.refund_status, @@ -2225,87 +2164,84 @@ pub struct AuthenticationErrorInformation { pub rmsg: String, } -impl - ForeignFrom<( - &Option, - &Option, - Option, - u16, - String, - )> for types::ErrorResponse -{ - fn foreign_from( - (error_data, risk_information, attempt_status, status_code, transaction_id): ( - &Option, - &Option, - Option, - u16, - String, - ), - ) -> Self { - let avs_message = risk_information - .clone() - .map(|client_risk_information| { - client_risk_information.rules.map(|rules| { - rules - .iter() - .map(|risk_info| { - risk_info.name.clone().map_or("".to_string(), |name| { - format!(" , {}", name.clone().expose()) - }) - }) - .collect::>() - .join("") - }) - }) - .unwrap_or(Some("".to_string())); - - let detailed_error_info = error_data.to_owned().and_then(|error_info| { - error_info.details.map(|error_details| { - error_details +fn get_error_response( + error_data: &Option, + risk_information: &Option, + attempt_status: Option, + status_code: u16, + transaction_id: String, +) -> ErrorResponse { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", ") + .map(|risk_info| { + risk_info.name.clone().map_or("".to_string(), |name| { + format!(" , {}", name.clone().expose()) + }) + }) + .collect::>() + .join("") }) - }); + }) + .unwrap_or(Some("".to_string())); + + let detailed_error_info = error_data.to_owned().and_then(|error_info| { + error_info.details.map(|error_details| { + error_details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") + }) + }); - let reason = get_error_reason( - error_data - .clone() - .and_then(|error_details| error_details.message), - detailed_error_info, - avs_message, - ); - let error_message = error_data + let reason = get_error_reason( + error_data .clone() - .and_then(|error_details| error_details.reason); - - Self { - code: error_message - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code, - attempt_status, - connector_transaction_id: Some(transaction_id.clone()), - } + .and_then(|error_details| error_details.message), + detailed_error_info, + avs_message, + ); + let error_message = error_data + .clone() + .and_then(|error_details| error_details.reason); + + ErrorResponse { + code: error_message + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status, + connector_transaction_id: Some(transaction_id.clone()), } } -impl TryFrom<(&types::SetupMandateRouterData, domain::Card)> for BankOfAmericaPaymentsRequest { +impl + TryFrom<( + &SetupMandateRouterData, + hyperswitch_domain_models::payment_method_data::Card, + )> for BankOfAmericaPaymentsRequest +{ type Error = error_stack::Report; fn try_from( - (item, ccard): (&types::SetupMandateRouterData, domain::Card), + (item, ccard): ( + &SetupMandateRouterData, + hyperswitch_domain_models::payment_method_data::Card, + ), ) -> Result { let order_information = OrderInformationWithBill::try_from(item)?; let client_reference_information = ClientReferenceInformation::from(item); - let merchant_defined_information = item.request.metadata.clone().map(|metadata| { - Vec::::foreign_from(metadata.peek().to_owned()) - }); + let merchant_defined_information = + item.request.metadata.clone().map(|metadata| { + convert_metadata_to_merchant_defined_info(metadata.peek().to_owned()) + }); let payment_information = PaymentInformation::try_from(&ccard)?; let processing_information = ProcessingInformation::try_from((None, None))?; Ok(Self { @@ -2319,29 +2255,28 @@ impl TryFrom<(&types::SetupMandateRouterData, domain::Card)> for BankOfAmericaPa } } -impl TryFrom<(&types::SetupMandateRouterData, domain::ApplePayWalletData)> - for BankOfAmericaPaymentsRequest -{ +impl TryFrom<(&SetupMandateRouterData, ApplePayWalletData)> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( - (item, apple_pay_data): (&types::SetupMandateRouterData, domain::ApplePayWalletData), + (item, apple_pay_data): (&SetupMandateRouterData, ApplePayWalletData), ) -> Result { let order_information = OrderInformationWithBill::try_from(item)?; let client_reference_information = ClientReferenceInformation::from(item); - let merchant_defined_information = item.request.metadata.clone().map(|metadata| { - Vec::::foreign_from(metadata.peek().to_owned()) - }); + let merchant_defined_information = + item.request.metadata.clone().map(|metadata| { + convert_metadata_to_merchant_defined_info(metadata.peek().to_owned()) + }); let payment_information = match item.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { PaymentInformation::try_from(&decrypt_data)? } - types::PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!( + PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!( "Apple Pay", "Manual", "Bank Of America" ))?, - types::PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Bank Of America"))? } }, @@ -2380,18 +2315,17 @@ impl TryFrom<(&types::SetupMandateRouterData, domain::ApplePayWalletData)> } } -impl TryFrom<(&types::SetupMandateRouterData, domain::GooglePayWalletData)> - for BankOfAmericaPaymentsRequest -{ +impl TryFrom<(&SetupMandateRouterData, GooglePayWalletData)> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( - (item, google_pay_data): (&types::SetupMandateRouterData, domain::GooglePayWalletData), + (item, google_pay_data): (&SetupMandateRouterData, GooglePayWalletData), ) -> Result { let order_information = OrderInformationWithBill::try_from(item)?; let client_reference_information = ClientReferenceInformation::from(item); - let merchant_defined_information = item.request.metadata.clone().map(|metadata| { - Vec::::foreign_from(metadata.peek().to_owned()) - }); + let merchant_defined_information = + item.request.metadata.clone().map(|metadata| { + convert_metadata_to_merchant_defined_info(metadata.peek().to_owned()) + }); let payment_information = PaymentInformation::from(&google_pay_data); let processing_information = ProcessingInformation::try_from((Some(PaymentSolution::GooglePay), None))?; @@ -2429,10 +2363,10 @@ impl TryFrom<(Option, Option)> for ProcessingInformatio } } -impl TryFrom<&types::SetupMandateRouterData> for OrderInformationWithBill { +impl TryFrom<&SetupMandateRouterData> for OrderInformationWithBill { type Error = error_stack::Report; - fn try_from(item: &types::SetupMandateRouterData) -> Result { + fn try_from(item: &SetupMandateRouterData) -> Result { let email = item.request.get_email()?; let bill_to = build_bill_to(item.get_optional_billing(), email)?; @@ -2446,10 +2380,12 @@ impl TryFrom<&types::SetupMandateRouterData> for OrderInformationWithBill { } } -impl TryFrom<&domain::Card> for PaymentInformation { +impl TryFrom<&hyperswitch_domain_models::payment_method_data::Card> for PaymentInformation { type Error = error_stack::Report; - fn try_from(ccard: &domain::Card) -> Result { + fn try_from( + ccard: &hyperswitch_domain_models::payment_method_data::Card, + ) -> Result { 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), @@ -2488,8 +2424,8 @@ impl TryFrom<&Box> for PaymentInformation { } } -impl From<&domain::ApplePayWalletData> for PaymentInformation { - fn from(apple_pay_data: &domain::ApplePayWalletData) -> Self { +impl From<&ApplePayWalletData> for PaymentInformation { + fn from(apple_pay_data: &ApplePayWalletData) -> Self { Self::ApplePayToken(Box::new(ApplePayTokenPaymentInformation { fluid_data: FluidData { value: Secret::from(apple_pay_data.payment_data.clone()), @@ -2501,8 +2437,8 @@ impl From<&domain::ApplePayWalletData> for PaymentInformation { } } -impl From<&domain::GooglePayWalletData> for PaymentInformation { - fn from(google_pay_data: &domain::GooglePayWalletData) -> Self { +impl From<&GooglePayWalletData> for PaymentInformation { + fn from(google_pay_data: &GooglePayWalletData) -> Self { Self::GooglePay(Box::new(GooglePayPaymentInformation { fluid_data: FluidData { value: Secret::from( @@ -2513,44 +2449,43 @@ impl From<&domain::GooglePayWalletData> for PaymentInformation { } } -impl ForeignFrom<(&BankOfAmericaErrorInformationResponse, u16)> for types::ErrorResponse { - fn foreign_from( - (error_response, status_code): (&BankOfAmericaErrorInformationResponse, u16), - ) -> Self { - let detailed_error_info = - error_response - .error_information - .to_owned() - .details - .map(|error_details| { - error_details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", ") - }); - - let reason = get_error_reason( - error_response.error_information.message.to_owned(), - detailed_error_info, - None, - ); - Self { - code: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), - } +fn convert_to_error_response_from_error_info( + error_response: &BankOfAmericaErrorInformationResponse, + status_code: u16, +) -> ErrorResponse { + let detailed_error_info = + error_response + .error_information + .to_owned() + .details + .map(|error_details| { + error_details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") + }); + + let reason = get_error_reason( + error_response.error_information.message.to_owned(), + detailed_error_info, + None, + ); + ErrorResponse { + code: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), } } diff --git a/crates/router/src/connector/cybersource.rs b/crates/hyperswitch_connectors/src/connectors/cybersource.rs similarity index 66% rename from crates/router/src/connector/cybersource.rs rename to crates/hyperswitch_connectors/src/connectors/cybersource.rs index 078f66335f..5a60f6d705 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource.rs @@ -1,40 +1,83 @@ pub mod transformers; use base64::Engine; +use common_enums::enums; use common_utils::{ - request::RequestContent, + consts, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use masking::{ExposeInterface, PeekInterface}; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + mandate_revoke::MandateRevoke, + payments::{ + Authorize, Capture, CompleteAuthorize, IncrementalAuthorization, PSync, + PaymentMethodToken, Session, SetupMandate, Void, + }, + refunds::{Execute, RSync}, + PreProcessing, + }, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, MandateRevokeRequestData, + PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, + }, + router_response_types::{MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData}, + types::{ + MandateRevokeRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData, + PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, + PaymentsIncrementalAuthorizationRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundExecuteRouterData, RefundSyncRouterData, + SetupMandateRouterData, + }, +}; +#[cfg(feature = "payouts")] +use hyperswitch_domain_models::{ + router_flow_types::payouts::PoFulfill, + router_response_types::PayoutsResponseData, + types::{PayoutsData, PayoutsRouterData}, +}; +#[cfg(feature = "payouts")] +use hyperswitch_interfaces::types::PayoutFulfillType; +use hyperswitch_interfaces::{ + api::{ + self, + payments::PaymentSession, + refunds::{Refund, RefundExecute, RefundSync}, + ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + IncrementalAuthorizationType, MandateRevokeType, PaymentsAuthorizeType, + PaymentsCaptureType, PaymentsCompleteAuthorizeType, PaymentsPreProcessingType, + PaymentsSyncType, PaymentsVoidType, RefundExecuteType, RefundSyncType, Response, + SetupMandateType, + }, + webhooks, +}; +use masking::{ExposeInterface, Mask, Maskable, PeekInterface}; use ring::{digest, hmac}; use time::OffsetDateTime; use transformers as cybersource; use url::Url; -use super::utils::{convert_amount, PaymentsAuthorizeRequestData, RouterData}; use crate::{ - configs::settings, - connector::{ - utils as connector_utils, - utils::{PaymentMethodDataType, RefundsRequestData}, - }, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignTryFrom, + constants::{self, headers}, + types::ResponseRouterData, + utils::{ + self, convert_amount, PaymentMethodDataType, PaymentsAuthorizeRequestData, + RefundsRequestData, RouterData as OtherRouterData, }, - utils::BytesExt, }; #[derive(Clone)] @@ -62,16 +105,16 @@ impl Cybersource { resource: &str, payload: &String, date: OffsetDateTime, - http_method: services::Method, + http_method: Method, ) -> CustomResult { let cybersource::CybersourceAuthType { api_key, merchant_account, api_secret, } = auth; - let is_post_method = matches!(http_method, services::Method::Post); - let is_patch_method = matches!(http_method, services::Method::Patch); - let is_delete_method = matches!(http_method, services::Method::Delete); + let is_post_method = matches!(http_method, Method::Post); + let is_patch_method = matches!(http_method, Method::Patch); + let is_delete_method = matches!(http_method, Method::Delete); let digest_str = if is_post_method || is_patch_method { "digest " } else { @@ -117,7 +160,7 @@ impl ConnectorCommon for Cybersource { "application/json;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.cybersource.base_url.as_ref() } @@ -127,18 +170,18 @@ impl ConnectorCommon for Cybersource { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: Result< cybersource::CybersourceErrorResponse, Report, > = res.response.parse_struct("Cybersource ErrorResponse"); let error_message = if res.status_code == 401 { - consts::CONNECTOR_UNAUTHORIZED_ERROR + constants::CONNECTOR_UNAUTHORIZED_ERROR } else { - consts::NO_ERROR_MESSAGE + hyperswitch_interfaces::consts::NO_ERROR_MESSAGE }; match response { Ok(transformers::CybersourceErrorResponse::StandardError(response)) => { @@ -173,12 +216,10 @@ impl ConnectorCommon for Cybersource { .join(", ") }); ( - response - .reason - .clone() - .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { - reason.to_string() - }), + response.reason.clone().map_or( + hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), + |reason| reason.to_string(), + ), response .reason .map_or(error_message.to_string(), |reason| reason.to_string()), @@ -191,7 +232,7 @@ impl ConnectorCommon for Cybersource { } }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, code, message, @@ -203,9 +244,9 @@ impl ConnectorCommon for Cybersource { Ok(transformers::CybersourceErrorResponse::AuthenticationError(response)) => { event_builder.map(|i| i.set_error_response_body(&response)); router_env::logger::info!(connector_response=?response); - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: response.response.rmsg.clone(), reason: Some(response.response.rmsg), attempt_status: None, @@ -227,9 +268,9 @@ impl ConnectorCommon for Cybersource { }) .collect::>() .join(" & "); - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: error_response.clone(), reason: Some(error_response), attempt_status: None, @@ -239,7 +280,7 @@ impl ConnectorCommon for Cybersource { 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}))); router_env::logger::error!(deserialization_error =? error_msg); - crate::utils::handle_json_response_deserialization_failure(res, "cybersource") + utils::handle_json_response_deserialization_failure(res, "cybersource") } } } @@ -258,21 +299,21 @@ impl ConnectorValidation for Cybersource { | 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()), + utils::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::Card, PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } @@ -282,9 +323,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let date = OffsetDateTime::now_utc(); let cybersource_req = self.get_request_body(req, connectors)?; let auth = cybersource::CybersourceAuthType::try_from(&req.connector_auth_type)?; @@ -328,10 +369,7 @@ where ("Host".to_string(), host.to_string().into()), ("Signature".to_string(), signature.into_masked()), ]; - if matches!( - http_method, - services::Method::Post | services::Method::Put | services::Method::Patch - ) { + if matches!(http_method, Method::Post | Method::Put | Method::Patch) { headers.push(( "Digest".to_string(), format!("SHA-256={sha256}").into_masked(), @@ -358,28 +396,20 @@ impl api::Payouts for Cybersource {} #[cfg(feature = "payouts")] impl api::PayoutFulfill for Cybersource {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Cybersource +impl ConnectorIntegration + for Cybersource { // Not Implemented (R) } -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Cybersource +impl ConnectorIntegration + for Cybersource { 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_content_type(&self) -> &'static str { @@ -387,15 +417,15 @@ impl } fn get_url( &self, - _req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &SetupMandateRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, + req: &SetupMandateRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = cybersource::CybersourceZeroMandateRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -403,35 +433,33 @@ 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) - .url(&types::SetupMandateType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&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, - )?) + .headers(SetupMandateType::get_headers(self, req, connectors)?) + .set_body(SetupMandateType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &SetupMandateRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourcePaymentsResponse = res .response .parse_struct("CybersourceSetupMandatesResponse") .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, @@ -440,17 +468,17 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -466,76 +494,72 @@ impl }, None => None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl - ConnectorIntegration< - api::MandateRevoke, - types::MandateRevokeRequestData, - types::MandateRevokeResponseData, - > for Cybersource +impl ConnectorIntegration + for Cybersource { fn get_headers( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &MandateRevokeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_http_method(&self) -> services::Method { - services::Method::Delete + fn get_http_method(&self) -> Method { + Method::Delete } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, + req: &MandateRevokeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}tms/v1/paymentinstruments/{}", self.base_url(connectors), - connector_utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? + utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? )) } fn build_request( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &MandateRevokeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Delete) - .url(&types::MandateRevokeType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Delete) + .url(&MandateRevokeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::MandateRevokeType::get_headers( - self, req, connectors, - )?) + .headers(MandateRevokeType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::MandateRevokeRouterData, + data: &MandateRevokeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { if matches!(res.status_code, 204) { event_builder.map(|i| i.set_response_body(&serde_json::json!({"mandate_status": common_enums::MandateStatus::Revoked.to_string()}))); - Ok(types::MandateRevokeRouterData { - response: Ok(types::MandateRevokeResponseData { + Ok(MandateRevokeRouterData { + response: Ok(MandateRevokeResponseData { mandate_status: common_enums::MandateStatus::Revoked, }), ..data.clone() @@ -553,9 +577,9 @@ impl }); router_env::logger::info!(connector_response=?response_string); - Ok(types::MandateRevokeRouterData { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), + Ok(MandateRevokeRouterData { + response: Err(ErrorResponse { + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: response_string.clone(), reason: Some(response_string), status_code: res.status_code, @@ -568,37 +592,28 @@ impl } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { // Not Implemented (R) } -impl api::PaymentSession for Cybersource {} +impl PaymentSession for Cybersource {} -impl ConnectorIntegration - for Cybersource -{ -} +impl ConnectorIntegration for Cybersource {} -impl - ConnectorIntegration< - api::PreProcessing, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, - > for Cybersource +impl ConnectorIntegration + for Cybersource { 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) } fn get_content_type(&self) -> &'static str { @@ -606,8 +621,8 @@ impl } fn get_url( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, ) -> CustomResult { let redirect_response = req.request.redirect_response.clone().ok_or( errors::ConnectorError::MissingRequiredField { @@ -627,8 +642,8 @@ impl } fn get_request_body( &self, - req: &types::PaymentsPreProcessingRouterData, - _connectors: &settings::Connectors, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, ) -> CustomResult { let minor_amount = req.request @@ -650,20 +665,18 @@ 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) - .url(&types::PaymentsPreProcessingType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsPreProcessingType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsPreProcessingType::get_headers( + .headers(PaymentsPreProcessingType::get_headers( self, req, connectors, )?) - .set_body(types::PaymentsPreProcessingType::get_request_body( + .set_body(PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -672,17 +685,17 @@ impl fn handle_response( &self, - data: &types::PaymentsPreProcessingRouterData, + data: &PaymentsPreProcessingRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourcePreProcessingResponse = res .response .parse_struct("Cybersource AuthEnrollmentResponse") .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, @@ -691,21 +704,19 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } @@ -715,8 +726,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -728,8 +739,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -743,18 +754,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(), @@ -762,11 +771,11 @@ impl ConnectorIntegration, - res: types::Response, + res: Response, ) -> CustomResult< - types::RouterData, + RouterData, errors::ConnectorError, > { let response: cybersource::CybersourcePaymentsResponse = res @@ -775,7 +784,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -802,38 +811,38 @@ impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } - fn get_http_method(&self) -> services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req .request @@ -853,31 +862,31 @@ 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: cybersource::CybersourceTransactionResponse = res .response .parse_struct("Cybersource PaymentSyncResponse") .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, @@ -885,21 +894,19 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } @@ -909,8 +916,8 @@ impl ConnectorIntegration CustomResult { if req.is_three_ds() && req.request.is_card() @@ -932,8 +939,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -959,18 +966,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) .set_body(self.get_request_body(req, connectors)?) .build(); @@ -979,10 +982,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { if data.is_three_ds() && data.request.is_card() && (data.request.connector_mandate_id().is_none() @@ -995,7 +998,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -1043,13 +1046,15 @@ impl ConnectorIntegration None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) @@ -1057,29 +1062,27 @@ impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { fn get_url( &self, - _req: &types::PayoutsRouterData, - connectors: &settings::Connectors, + _req: &PayoutsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}pts/v2/payouts", self.base_url(connectors))) } fn get_headers( &self, - req: &types::PayoutsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PayoutsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_request_body( &self, - req: &types::PayoutsRouterData, - _connectors: &settings::Connectors, + req: &PayoutsRouterData, + _connectors: &Connectors, ) -> CustomResult { let amount = convert_amount( self.amount_converter, @@ -1094,19 +1097,15 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PayoutFulfillType::get_url(self, req, connectors)?) + req: &PayoutsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&PayoutFulfillType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PayoutFulfillType::get_headers( - self, req, connectors, - )?) - .set_body(types::PayoutFulfillType::get_request_body( - self, req, connectors, - )?) + .headers(PayoutFulfillType::get_headers(self, req, connectors)?) + .set_body(PayoutFulfillType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) @@ -1114,10 +1113,10 @@ impl ConnectorIntegration, + data: &PayoutsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: cybersource::CybersourceFulfillResponse = res .response .parse_struct("CybersourceFulfillResponse") @@ -1126,7 +1125,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -1161,31 +1160,29 @@ impl ConnectorIntegration None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Cybersource +impl ConnectorIntegration + for Cybersource { 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 { @@ -1193,8 +1190,8 @@ impl } fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}pts/v2/payments/", @@ -1203,8 +1200,8 @@ impl } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let amount = convert_amount( self.amount_converter, @@ -1218,20 +1215,20 @@ 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) - .url(&types::PaymentsCompleteAuthorizeType::get_url( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::PaymentsCompleteAuthorizeType::get_headers( + .headers(PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -1240,17 +1237,17 @@ impl fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourcePaymentsResponse = res .response .parse_struct("Cybersource 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, @@ -1259,17 +1256,17 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -1285,34 +1282,34 @@ impl }, None => None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -1327,8 +1324,8 @@ impl ConnectorIntegration CustomResult { let minor_amount = req.request @@ -1352,15 +1349,15 @@ 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)?) + RequestBuilder::new() + .method(Method::Post) + .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)?) .set_body(self.get_request_body(req, connectors)?) .build(), )) @@ -1368,17 +1365,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourcePaymentsResponse = res .response .parse_struct("Cybersource 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, @@ -1387,17 +1384,17 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: cybersource::CybersourceServerErrorResponse = res .response .parse_struct("CybersourceServerErrorResponse") @@ -1406,32 +1403,32 @@ impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } @@ -1441,8 +1438,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -1454,8 +1451,8 @@ impl ConnectorIntegration CustomResult { let refund_amount = convert_amount( self.amount_converter, @@ -1469,17 +1466,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundExecuteRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) .set_body(self.get_request_body(req, connectors)?) .build(), )) @@ -1487,17 +1482,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourceRefundResponse = res .response .parse_struct("Cybersource 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, @@ -1505,34 +1500,32 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } #[allow(dead_code)] -impl ConnectorIntegration - for Cybersource -{ +impl ConnectorIntegration for Cybersource { 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) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } - fn get_http_method(&self) -> services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -1543,31 +1536,31 @@ 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: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: cybersource::CybersourceRsyncResponse = res .response .parse_struct("Cybersource 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, @@ -1575,30 +1568,30 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration< - api::IncrementalAuthorization, - types::PaymentsIncrementalAuthorizationData, - types::PaymentsResponseData, + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, > for Cybersource { fn get_headers( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_http_method(&self) -> services::Method { - services::Method::Patch + fn get_http_method(&self) -> Method { + Method::Patch } fn get_content_type(&self) -> &'static str { @@ -1607,8 +1600,8 @@ impl fn get_url( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -1620,8 +1613,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - _connectors: &settings::Connectors, + req: &PaymentsIncrementalAuthorizationRouterData, + _connectors: &Connectors, ) -> CustomResult { let minor_additional_amount = MinorUnit::new(req.request.additional_amount); let additional_amount = convert_amount( @@ -1639,20 +1632,20 @@ impl } fn build_request( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Patch) - .url(&types::IncrementalAuthorizationType::get_url( + RequestBuilder::new() + .method(Method::Patch) + .url(&IncrementalAuthorizationType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::IncrementalAuthorizationType::get_headers( + .headers(IncrementalAuthorizationType::get_headers( self, req, connectors, )?) - .set_body(types::IncrementalAuthorizationType::get_request_body( + .set_body(IncrementalAuthorizationType::get_request_body( self, req, connectors, )?) .build(), @@ -1660,14 +1653,14 @@ impl } fn handle_response( &self, - data: &types::PaymentsIncrementalAuthorizationRouterData, + data: &PaymentsIncrementalAuthorizationRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, + res: Response, ) -> CustomResult< - types::RouterData< - api::IncrementalAuthorization, - types::PaymentsIncrementalAuthorizationData, - types::PaymentsResponseData, + RouterData< + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, >, errors::ConnectorError, > { @@ -1677,44 +1670,41 @@ impl .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; 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, - }, - true, - )) + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } #[async_trait::async_trait] -impl api::IncomingWebhook for Cybersource { +impl webhooks::IncomingWebhook for Cybersource { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs similarity index 77% rename from crates/router/src/connector/cybersource/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 7ce98d3d5a..9f3d024171 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -2,40 +2,68 @@ use api_models::payments; #[cfg(feature = "payouts")] use api_models::payouts::PayoutMethodData; use base64::Engine; -use common_enums::FutureUsage; +use common_enums::{enums, FutureUsage}; use common_utils::{ + consts, ext_traits::{OptionExt, ValueExt}, pii, types::{SemanticVersion, StringMajorUnit}, }; use error_stack::ResultExt; #[cfg(feature = "payouts")] -use hyperswitch_domain_models::address::{AddressDetails, PhoneDetails}; +use hyperswitch_domain_models::{ + address::{AddressDetails, PhoneDetails}, + router_flow_types::PoFulfill, + router_response_types::PayoutsResponseData, + types::PayoutsRouterData, +}; +use hyperswitch_domain_models::{ + payment_method_data::{ + ApplePayWalletData, GooglePayWalletData, NetworkTokenData, PaymentMethodData, + SamsungPayWalletData, WalletData, + }, + router_data::{ + AdditionalPaymentMethodConnectorResponse, ApplePayPredecryptData, ConnectorAuthType, + ConnectorResponseData, ErrorResponse, PaymentMethodToken, RouterData, + }, + router_flow_types::{ + payments::Authorize, + refunds::{Execute, RSync}, + SetupMandate, + }, + router_request_types::{ + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsPreProcessingData, PaymentsSyncData, ResponseId, SetupMandateRequestData, + }, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsIncrementalAuthorizationRouterData, + PaymentsPreProcessingRouterData, RefundsRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{api, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use utils::ForeignTryFrom; #[cfg(feature = "payouts")] -use crate::connector::utils::PayoutsData; +use crate::types::PayoutsResponseRouterData; +#[cfg(feature = "payouts")] +use crate::utils::PayoutsData; use crate::{ - connector::utils::{ - self, AddressDetailsData, ApplePayDecrypt, CardData, NetworkTokenData, + constants, + types::{RefundsResponseRouterData, ResponseRouterData}, + unimplemented_payment_method, + utils::{ + self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, NetworkTokenData as _, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - PaymentsPreProcessingData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData, - RecurringMandateData, RouterData, - }, - consts, - core::errors, - services, - types::{ - self, - api::{self, enums as api_enums}, - domain, - storage::enums, - transformers::{ForeignFrom, ForeignTryFrom}, - ApplePayPredecryptData, + PaymentsPreProcessingRequestData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData, + RecurringMandateData, RouterData as OtherRouterData, }, - unimplemented_payment_method, }; #[derive(Debug, Serialize)] @@ -53,6 +81,22 @@ impl From<(StringMajorUnit, T)> for CybersourceRouterData { } } +impl From for String { + fn from(card_issuer: CardIssuer) -> Self { + let card_type = match card_issuer { + CardIssuer::AmericanExpress => "003", + CardIssuer::Master => "002", + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + CardIssuer::Maestro => "042", + CardIssuer::Visa => "001", + CardIssuer::Discover => "004", + CardIssuer::DinersClub => "005", + CardIssuer::CarteBlanche => "006", + CardIssuer::JCB => "007", + }; + card_type.to_string() + } +} #[derive(Debug, Default, Serialize, Deserialize)] pub struct CybersourceConnectorMetadataObject { pub disable_avs: Option, @@ -79,9 +123,9 @@ pub struct CybersourceZeroMandateRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { +impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { type Error = error_stack::Report; - fn try_from(item: &types::SetupMandateRouterData) -> Result { + fn try_from(item: &SetupMandateRouterData) -> Result { let email = item.get_billing_email().or(item.request.get_email())?; let bill_to = build_bill_to(item.get_optional_billing(), email)?; @@ -118,7 +162,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { }; let (payment_information, solution) = match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { let card_type = match ccard .card_network .clone() @@ -141,55 +185,54 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { ) } - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { - match item.payment_method_token.clone() { - Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let expiration_month = decrypt_data.get_expiry_month()?; - let expiration_year = decrypt_data.get_four_digit_expiry_year()?; - ( - PaymentInformation::ApplePay(Box::new( - ApplePayPaymentInformation { - tokenized_card: TokenizedCard { - number: decrypt_data - .application_primary_account_number, - cryptogram: decrypt_data - .payment_data - .online_payment_cryptogram, - transaction_type: TransactionType::ApplePay, - expiration_year, - expiration_month, - }, + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => match item.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + let expiration_month = decrypt_data.get_expiry_month()?; + let expiration_year = decrypt_data.get_four_digit_expiry_year()?; + ( + PaymentInformation::ApplePay(Box::new( + ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: decrypt_data.application_primary_account_number, + cryptogram: decrypt_data + .payment_data + .online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, }, - )), - Some(PaymentSolution::ApplePay), - ) - } - 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( - ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, }, + )), + Some(PaymentSolution::ApplePay), + ) + } + PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Cybersource" + ))?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Cybersource"))? + } + }, + None => ( + PaymentInformation::ApplePayToken(Box::new( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), }, - )), - Some(PaymentSolution::ApplePay), - ), - } - } - domain::WalletData::GooglePay(google_pay_data) => ( + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + )), + Some(PaymentSolution::ApplePay), + ), + }, + WalletData::GooglePay(google_pay_data) => ( PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { fluid_data: FluidData { value: Secret::from( @@ -201,52 +244,52 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { })), Some(PaymentSolution::GooglePay), ), - 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::Paze(_) - | 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::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("Cybersource"), ))?, }, - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + 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("Cybersource"), ))? @@ -568,24 +611,22 @@ pub struct BillTo { administrative_area: Option>, #[serde(skip_serializing_if = "Option::is_none")] postal_code: Option>, - country: Option, + country: Option, email: pii::Email, } -impl From<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> - for ClientReferenceInformation -{ - fn from(item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { +impl From<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for ClientReferenceInformation { + fn from(item: &CybersourceRouterData<&PaymentsAuthorizeRouterData>) -> Self { Self { code: Some(item.router_data.connector_request_reference_id.clone()), } } } -impl From<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>> +impl From<&CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>> for ClientReferenceInformation { - fn from(item: &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self { + fn from(item: &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>) -> Self { Self { code: Some(item.router_data.connector_request_reference_id.clone()), } @@ -594,7 +635,7 @@ impl From<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>> impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Option, Option, )> for ProcessingInformation @@ -602,7 +643,7 @@ impl type Error = error_stack::Report; fn try_from( (item, solution, network): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Option, Option, ), @@ -632,18 +673,15 @@ impl .router_data .request .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, FutureUsage::OffSession) - }) + == Some(FutureUsage::OffSession) && (item.router_data.request.customer_acceptance.is_some() || item .router_data .request .setup_mandate_details .clone() - .map_or(false, |mandate_details| { - mandate_details.customer_acceptance.is_some() - })) { + .is_some_and(|mandate_details| mandate_details.customer_acceptance.is_some())) + { ( Some(vec![CybersourceActionsList::TokenCreate]), Some(vec![ @@ -944,7 +982,7 @@ fn get_commerce_indicator_for_external_authentication( impl TryFrom<( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, Option, &CybersourceConsumerAuthValidateResponse, )> for ProcessingInformation @@ -952,7 +990,7 @@ impl type Error = error_stack::Report; fn try_from( (item, solution, three_ds_data): ( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, Option, &CybersourceConsumerAuthValidateResponse, ), @@ -960,35 +998,30 @@ impl let connector_merchant_config = CybersourceConnectorMetadataObject::try_from(&item.router_data.connector_meta_data)?; - let (action_list, action_token_types, authorization_options) = if item - .router_data - .request - .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, FutureUsage::OffSession) - }) - //TODO check for customer acceptance also - { - ( - Some(vec![CybersourceActionsList::TokenCreate]), - Some(vec![ - CybersourceActionsTokenType::PaymentInstrument, - CybersourceActionsTokenType::Customer, - ]), - Some(CybersourceAuthorizationOptions { - initiator: Some(CybersourcePaymentInitiator { - initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), - credential_stored_on_file: Some(true), - stored_credential_used: None, + let (action_list, action_token_types, authorization_options) = + if item.router_data.request.setup_future_usage == Some(FutureUsage::OffSession) + //TODO check for customer acceptance also + { + ( + Some(vec![CybersourceActionsList::TokenCreate]), + Some(vec![ + CybersourceActionsTokenType::PaymentInstrument, + CybersourceActionsTokenType::Customer, + ]), + Some(CybersourceAuthorizationOptions { + initiator: Some(CybersourcePaymentInitiator { + initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }), + merchant_intitiated_transaction: None, + ignore_avs_result: connector_merchant_config.disable_avs, + ignore_cv_result: connector_merchant_config.disable_cvn, }), - merchant_intitiated_transaction: None, - ignore_avs_result: connector_merchant_config.disable_avs, - ignore_cv_result: connector_merchant_config.disable_cvn, - }), - ) - } else { - (None, None, None) - }; + ) + } else { + (None, None, None) + }; Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, @@ -1009,13 +1042,13 @@ impl impl From<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Option, )> for OrderInformationWithBill { fn from( (item, bill_to): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Option, ), ) -> Self { @@ -1031,13 +1064,13 @@ impl impl From<( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, BillTo, )> for OrderInformationWithBill { fn from( (item, bill_to): ( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, BillTo, ), ) -> Self { @@ -1129,35 +1162,32 @@ fn build_bill_to( .unwrap_or(default_address)) } -impl ForeignFrom for Vec { - fn foreign_from(metadata: Value) -> Self { - let hashmap: std::collections::BTreeMap = - serde_json::from_str(&metadata.to_string()) - .unwrap_or(std::collections::BTreeMap::new()); - let mut vector: Self = Self::new(); - let mut iter = 1; - for (key, value) in hashmap { - vector.push(MerchantDefinedInformation { - key: iter, - value: format!("{key}={value}"), - }); - iter += 1; - } - vector +fn convert_metadata_to_merchant_defined_info(metadata: Value) -> Vec { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()).unwrap_or(std::collections::BTreeMap::new()); + let mut vector = Vec::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; } + vector } impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { let email = item @@ -1204,7 +1234,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let consumer_authentication_information = item .router_data @@ -1246,14 +1276,14 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, hyperswitch_domain_models::payment_method_data::CardDetailsForNetworkTransactionId, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, hyperswitch_domain_models::payment_method_data::CardDetailsForNetworkTransactionId, ), ) -> Result { @@ -1287,7 +1317,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let consumer_authentication_information = item .router_data @@ -1329,15 +1359,15 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::NetworkTokenData, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + NetworkTokenData, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, token_data): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::NetworkTokenData, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + NetworkTokenData, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -1368,7 +1398,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let consumer_authentication_information = item .router_data @@ -1410,14 +1440,14 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Box, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, paze_data): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Box, ), ) -> Result { @@ -1481,7 +1511,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -1496,15 +1526,15 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, - domain::Card, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, - domain::Card, + &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { let email = item @@ -1568,7 +1598,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -1583,17 +1613,17 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, apple_pay_data, apple_pay_wallet_data): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, ), ) -> Result { let email = item @@ -1625,7 +1655,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let ucaf_collection_indicator = match apple_pay_wallet_data .payment_method .network @@ -1657,15 +1687,15 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, google_pay_data): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, ), ) -> Result { let email = item @@ -1692,7 +1722,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -1707,15 +1737,15 @@ impl impl TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - Box, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + Box, )> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, samsung_pay_data): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - Box, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + Box, ), ) -> Result { let email = item @@ -1756,7 +1786,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -1792,33 +1822,31 @@ fn get_samsung_pay_fluid_data_value( Ok(samsung_pay_fluid_data_value) } -impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> - for CybersourcePaymentsRequest -{ +impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + item: &CybersourceRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.connector_mandate_id() { Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), None => { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { + PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => { match item.router_data.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { Self::try_from((item, decrypt_data, apple_pay_data)) } - types::PaymentMethodToken::Token(_) => { + PaymentMethodToken::Token(_) => { Err(unimplemented_payment_method!( "Apple Pay", "Manual", "Cybersource" ))? } - types::PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Cybersource"))? } }, @@ -1854,9 +1882,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> ); let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from( - metadata, - ) + convert_metadata_to_merchant_defined_info(metadata) }); let ucaf_collection_indicator = match apple_pay_data .payment_method @@ -1889,17 +1915,17 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> } } } - domain::WalletData::GooglePay(google_pay_data) => { + WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } - domain::WalletData::SamsungPay(samsung_pay_data) => { + WalletData::SamsungPay(samsung_pay_data) => { Self::try_from((item, samsung_pay_data)) } - domain::WalletData::Paze(_) => { + WalletData::Paze(_) => { match item.router_data.payment_method_token.clone() { - Some(types::PaymentMethodToken::PazeDecrypt( - paze_decrypted_data, - )) => Self::try_from((item, paze_decrypted_data)), + Some(PaymentMethodToken::PazeDecrypt(paze_decrypted_data)) => { + Self::try_from((item, paze_decrypted_data)) + } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message( "Cybersource", @@ -1908,41 +1934,37 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> .into()), } } - 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::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( - utils::get_unimplemented_payment_method_error_message( - "Cybersource", - ), - ) - .into()) - } + 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::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("Cybersource"), + ) + .into()), }, // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. // This is a fallback implementation in the event of catastrophe. - domain::PaymentMethodData::MandatePayment => { + PaymentMethodData::MandatePayment => { let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or( errors::ConnectorError::MissingRequiredField { @@ -1951,26 +1973,26 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> )?; Self::try_from((item, connector_mandate_id)) } - domain::PaymentMethodData::NetworkToken(token_data) => { + PaymentMethodData::NetworkToken(token_data) => { Self::try_from((item, token_data)) } - domain::PaymentMethodData::CardDetailsForNetworkTransactionId(card) => { + PaymentMethodData::CardDetailsForNetworkTransactionId(card) => { Self::try_from((item, card)) } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | 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(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), ) @@ -1982,16 +2004,13 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl - TryFrom<( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - String, - )> for CybersourcePaymentsRequest +impl TryFrom<(&CybersourceRouterData<&PaymentsAuthorizeRouterData>, String)> + for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( (item, connector_mandate_id): ( - &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, String, ), ) -> Result { @@ -2015,7 +2034,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, payment_information, @@ -2034,15 +2053,13 @@ pub struct CybersourceAuthSetupRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> - for CybersourceAuthSetupRequest -{ +impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for CybersourceAuthSetupRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + item: &CybersourceRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { let card_type = match ccard .card_network .clone() @@ -2067,24 +2084,24 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> client_reference_information, }) } - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + 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("Cybersource"), ) @@ -2111,19 +2128,20 @@ pub struct CybersourcePaymentsIncrementalAuthorizationRequest { order_information: OrderInformationIncrementalAuthorization, } -impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> +impl TryFrom<&CybersourceRouterData<&PaymentsCaptureRouterData>> for CybersourcePaymentsCaptureRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsCaptureRouterData>, + item: &CybersourceRouterData<&PaymentsCaptureRouterData>, ) -> Result { let merchant_defined_information = item .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); + Ok(Self { processing_information: ProcessingInformation { capture_options: Some(CaptureOptions { @@ -2152,12 +2170,12 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> } } -impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> +impl TryFrom<&CybersourceRouterData<&PaymentsIncrementalAuthorizationRouterData>> for CybersourcePaymentsIncrementalAuthorizationRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>, + item: &CybersourceRouterData<&PaymentsIncrementalAuthorizationRouterData>, ) -> Result { let connector_merchant_config = CybersourceConnectorMetadataObject::try_from(&item.router_data.connector_meta_data)?; @@ -2212,17 +2230,18 @@ pub struct ReversalInformation { reason: String, } -impl TryFrom<&CybersourceRouterData<&types::PaymentsCancelRouterData>> for CybersourceVoidRequest { +impl TryFrom<&CybersourceRouterData<&PaymentsCancelRouterData>> for CybersourceVoidRequest { type Error = error_stack::Report; fn try_from( - value: &CybersourceRouterData<&types::PaymentsCancelRouterData>, + value: &CybersourceRouterData<&PaymentsCancelRouterData>, ) -> Result { let merchant_defined_information = value .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); + Ok(Self { client_reference_information: ClientReferenceInformation { code: Some(value.router_data.connector_request_reference_id.clone()), @@ -2256,10 +2275,10 @@ pub struct CybersourceAuthType { pub(super) api_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for CybersourceAuthType { +impl TryFrom<&ConnectorAuthType> for CybersourceAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -2309,40 +2328,42 @@ pub enum CybersourceIncrementalAuthorizationStatus { AuthorizedPendingReview, } -impl ForeignFrom<(CybersourcePaymentStatus, bool)> for enums::AttemptStatus { - fn foreign_from((status, capture): (CybersourcePaymentStatus, bool)) -> Self { - match status { - CybersourcePaymentStatus::Authorized => { - if capture { - // Because Cybersource will return Payment Status as Authorized even in AutoCapture Payment - Self::Charged - } else { - Self::Authorized - } - } - CybersourcePaymentStatus::Succeeded | CybersourcePaymentStatus::Transmitted => { - Self::Charged +pub fn map_cybersource_attempt_status( + status: CybersourcePaymentStatus, + capture: bool, +) -> enums::AttemptStatus { + match status { + CybersourcePaymentStatus::Authorized => { + if capture { + // Because Cybersource will return Payment Status as Authorized even in AutoCapture Payment + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } - CybersourcePaymentStatus::Voided - | CybersourcePaymentStatus::Reversed - | CybersourcePaymentStatus::Cancelled => Self::Voided, - CybersourcePaymentStatus::Failed - | CybersourcePaymentStatus::Declined - | CybersourcePaymentStatus::AuthorizedRiskDeclined - | CybersourcePaymentStatus::Rejected - | CybersourcePaymentStatus::InvalidRequest - | CybersourcePaymentStatus::ServerError => Self::Failure, - CybersourcePaymentStatus::PendingAuthentication => Self::AuthenticationPending, - CybersourcePaymentStatus::PendingReview - | CybersourcePaymentStatus::StatusNotReceived - | CybersourcePaymentStatus::Challenge - | CybersourcePaymentStatus::Accepted - | CybersourcePaymentStatus::Pending - | CybersourcePaymentStatus::AuthorizedPendingReview => Self::Pending, } + CybersourcePaymentStatus::Succeeded | CybersourcePaymentStatus::Transmitted => { + enums::AttemptStatus::Charged + } + CybersourcePaymentStatus::Voided + | CybersourcePaymentStatus::Reversed + | CybersourcePaymentStatus::Cancelled => enums::AttemptStatus::Voided, + CybersourcePaymentStatus::Failed + | CybersourcePaymentStatus::Declined + | CybersourcePaymentStatus::AuthorizedRiskDeclined + | CybersourcePaymentStatus::Rejected + | CybersourcePaymentStatus::InvalidRequest + | CybersourcePaymentStatus::ServerError => enums::AttemptStatus::Failure, + CybersourcePaymentStatus::PendingAuthentication => { + enums::AttemptStatus::AuthenticationPending + } + CybersourcePaymentStatus::PendingReview + | CybersourcePaymentStatus::StatusNotReceived + | CybersourcePaymentStatus::Challenge + | CybersourcePaymentStatus::Accepted + | CybersourcePaymentStatus::Pending + | CybersourcePaymentStatus::AuthorizedPendingReview => enums::AttemptStatus::Pending, } } - impl From for common_enums::AuthorizationStatus { fn from(item: CybersourceIncrementalAuthorizationStatus) -> Self { match item { @@ -2454,84 +2475,17 @@ pub struct CybersourceErrorInformation { details: Option>, } -impl - ForeignFrom<( - &CybersourceErrorInformationResponse, - types::ResponseRouterData, - Option, - )> for types::RouterData -{ - fn foreign_from( - (error_response, item, transaction_status): ( - &CybersourceErrorInformationResponse, - types::ResponseRouterData< - F, - CybersourcePaymentsResponse, - T, - types::PaymentsResponseData, - >, - Option, - ), - ) -> Self { - let detailed_error_info = - error_response - .error_information - .details - .to_owned() - .map(|details| { - details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", ") - }); - - let reason = get_error_reason( - error_response.error_information.message.clone(), - detailed_error_info, - None, - ); - let response = Err(types::ErrorResponse { - code: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), - }); - match transaction_status { - Some(status) => Self { - response, - status, - ..item.data - }, - None => Self { - response, - ..item.data - }, - } - } -} - fn get_error_response_if_failure( (info_response, status, http_code): (&CybersourcePaymentsResponse, enums::AttemptStatus, u16), -) -> Option { +) -> Option { if utils::is_payment_failure(status) { - Some(types::ErrorResponse::foreign_from(( + Some(get_error_response( &info_response.error_information, &info_response.risk_information, Some(status), http_code, info_response.id.clone(), - ))) + )) } else { None } @@ -2539,7 +2493,7 @@ fn get_error_response_if_failure( fn get_payment_response( (info_response, status, http_code): (&CybersourcePaymentsResponse, enums::AttemptStatus, u16), -) -> Result { +) -> Result { let error_response = get_error_response_if_failure((info_response, status, http_code)); match error_response { Some(error) => Err(error), @@ -2550,7 +2504,7 @@ fn get_payment_response( info_response .token_information .clone() - .map(|token_info| types::MandateReference { + .map(|token_info| MandateReference { connector_mandate_id: token_info .payment_instrument .map(|payment_instrument| payment_instrument.id.expose()), @@ -2559,8 +2513,8 @@ fn get_payment_response( connector_mandate_request_reference_id: None, }); - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(info_response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -2583,38 +2537,37 @@ fn get_payment_response( impl TryFrom< - types::ResponseRouterData< - api::Authorize, + ResponseRouterData< + Authorize, CybersourcePaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, - > - for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::Authorize, + item: ResponseRouterData< + Authorize, CybersourcePaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_cybersource_attempt_status( item.response .status .clone() .unwrap_or(CybersourcePaymentStatus::StatusNotReceived), item.data.request.is_auto_capture()?, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); let connector_response = item .response .processor_information .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + .map(AdditionalPaymentMethodConnectorResponse::from) + .map(ConnectorResponseData::with_additional_payment_method_data); Ok(Self { status, @@ -2627,41 +2580,39 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourceAuthSetupResponse, - 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, CybersourceAuthSetupResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { CybersourceAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - 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, - }, - )), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(Some(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, @@ -2697,11 +2648,13 @@ impl ); let error_message = error_response.error_information.reason; Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: error_message .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or( + hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string(), + ), reason, status_code: item.http_code, attempt_status: None, @@ -2758,12 +2711,12 @@ pub enum CybersourcePreProcessingRequest { AuthValidate(Box), } -impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> +impl TryFrom<&CybersourceRouterData<&PaymentsPreProcessingRouterData>> for CybersourcePreProcessingRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsPreProcessingRouterData>, + item: &CybersourceRouterData<&PaymentsPreProcessingRouterData>, ) -> Result { let client_reference_information = ClientReferenceInformation { code: Some(item.router_data.connector_request_reference_id.clone()), @@ -2774,7 +2727,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> }, )?; let payment_information = match payment_method_data { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { let card_type = match ccard .card_network .clone() @@ -2795,24 +2748,24 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> }, ))) } - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + 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("Cybersource"), )) @@ -2897,12 +2850,12 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> } } -impl TryFrom<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>> +impl TryFrom<&CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>> for CybersourcePaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + item: &CybersourceRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( errors::ConnectorError::MissingRequiredField { @@ -2910,25 +2863,25 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData> }, )?; match payment_method_data { - domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + 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("Cybersource"), ) @@ -3003,21 +2956,21 @@ impl From for enums::AttemptStatus { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourcePreProcessingResponse, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, + PaymentsPreProcessingData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, CybersourcePreProcessingResponse, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, + PaymentsPreProcessingData, + PaymentsResponseData, >, ) -> Result { match item.response { @@ -3025,13 +2978,13 @@ impl let status = enums::AttemptStatus::from(info_response.status); let risk_info: Option = None; if utils::is_payment_failure(status) { - let response = Err(types::ErrorResponse::foreign_from(( + let response = Err(get_error_response( &info_response.error_information, &risk_info, Some(status), item.http_code, info_response.id.clone(), - ))); + )); Ok(Self { status, @@ -3055,7 +3008,7 @@ impl .step_up_url, ) { (Some(token), Some(step_up_url)) => { - Some(services::RedirectForm::CybersourceConsumerAuth { + Some(RedirectForm::CybersourceConsumerAuth { access_token: token.expose(), step_up_url, }) @@ -3070,8 +3023,8 @@ impl .change_context(errors::ConnectorError::ResponseHandlingFailed)?; Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(redirection_data), mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!({ @@ -3106,11 +3059,12 @@ impl None, ); let error_message = error_response.error_information.reason.to_owned(); - let response = Err(types::ErrorResponse { + let response = Err(ErrorResponse { code: error_message .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), reason, status_code: item.http_code, attempt_status: None, @@ -3128,37 +3082,37 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourcePaymentsResponse, - 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, CybersourcePaymentsResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_cybersource_attempt_status( item.response .status .clone() .unwrap_or(CybersourcePaymentStatus::StatusNotReceived), item.data.request.is_auto_capture()?, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); let connector_response = item .response .processor_information .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + .map(AdditionalPaymentMethodConnectorResponse::from) + .map(ConnectorResponseData::with_additional_payment_method_data); Ok(Self { status, @@ -3169,7 +3123,7 @@ impl } } -impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnectorResponse { +impl From<&ClientProcessorInformation> for AdditionalPaymentMethodConnectorResponse { fn from(processor_information: &ClientProcessorInformation) -> Self { let payment_checks = Some( serde_json::json!({"avs_response": processor_information.avs, "card_verification": processor_information.card_verification}), @@ -3184,30 +3138,30 @@ impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnect impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourcePaymentsResponse, - 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, CybersourcePaymentsResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_cybersource_attempt_status( item.response .status .clone() .unwrap_or(CybersourcePaymentStatus::StatusNotReceived), true, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); Ok(Self { status, @@ -3219,30 +3173,30 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourcePaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, + PaymentsCancelData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, CybersourcePaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, + PaymentsCancelData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_cybersource_attempt_status( item.response .status .clone() .unwrap_or(CybersourcePaymentStatus::StatusNotReceived), false, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); Ok(Self { status, @@ -3255,33 +3209,28 @@ impl // zero dollar response impl TryFrom< - types::ResponseRouterData< - api::SetupMandate, + ResponseRouterData< + SetupMandate, CybersourcePaymentsResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, - > - for types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::SetupMandate, + item: ResponseRouterData< + SetupMandate, CybersourcePaymentsResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, ) -> Result { let mandate_reference = item.response .token_information .clone() - .map(|token_info| types::MandateReference { + .map(|token_info| MandateReference { connector_mandate_id: token_info .payment_instrument .map(|payment_instrument| payment_instrument.id.expose()), @@ -3289,13 +3238,13 @@ impl mandate_metadata: None, connector_mandate_request_reference_id: None, }); - let mut mandate_status = enums::AttemptStatus::foreign_from(( + let mut mandate_status = map_cybersource_attempt_status( item.response .status .clone() .unwrap_or(CybersourcePaymentStatus::StatusNotReceived), false, - )); + ); if matches!(mandate_status, enums::AttemptStatus::Authorized) { //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. mandate_status = enums::AttemptStatus::Charged @@ -3307,17 +3256,15 @@ impl .response .processor_information .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + .map(AdditionalPaymentMethodConnectorResponse::from) + .map(ConnectorResponseData::with_additional_payment_method_data); Ok(Self { status: mandate_status, response: match error_response { Some(error) => Err(error), - None => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), + None => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -3347,47 +3294,38 @@ impl } impl - ForeignTryFrom<( - types::ResponseRouterData< + TryFrom< + ResponseRouterData< F, CybersourcePaymentsIncrementalAuthorizationResponse, T, - types::PaymentsResponseData, + PaymentsResponseData, >, - bool, - )> for types::RouterData + > for RouterData { type Error = error_stack::Report; - fn foreign_try_from( - data: ( - types::ResponseRouterData< - F, - CybersourcePaymentsIncrementalAuthorizationResponse, - T, - types::PaymentsResponseData, - >, - bool, - ), + fn try_from( + item: ResponseRouterData< + F, + CybersourcePaymentsIncrementalAuthorizationResponse, + T, + PaymentsResponseData, + >, ) -> Result { - let item = data.0; Ok(Self { response: match item.response.error_information { - Some(error) => Ok( - types::PaymentsResponseData::IncrementalAuthorizationResponse { - status: common_enums::AuthorizationStatus::Failure, - error_code: error.reason, - error_message: error.message, - connector_authorization_id: None, - }, - ), - _ => Ok( - types::PaymentsResponseData::IncrementalAuthorizationResponse { - status: item.response.status.into(), - error_code: None, - error_message: None, - connector_authorization_id: None, - }, - ), + Some(error) => Ok(PaymentsResponseData::IncrementalAuthorizationResponse { + status: common_enums::AuthorizationStatus::Failure, + error_code: error.reason, + error_message: error.message, + connector_authorization_id: None, + }), + None => Ok(PaymentsResponseData::IncrementalAuthorizationResponse { + status: item.response.status.into(), + error_code: None, + error_message: None, + connector_authorization_id: None, + }), }, ..item.data }) @@ -3411,49 +3349,47 @@ pub struct ApplicationInformation { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, CybersourceTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, CybersourceTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, ) -> Result { match item.response.application_information.status { Some(status) => { - let status = enums::AttemptStatus::foreign_from(( - status, - item.data.request.is_auto_capture()?, - )); + let status = + map_cybersource_attempt_status(status, item.data.request.is_auto_capture()?); let incremental_authorization_allowed = Some(status == enums::AttemptStatus::Authorized); let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { - response: Err(types::ErrorResponse::foreign_from(( + response: Err(get_error_response( &item.response.error_information, &risk_info, Some(status), item.http_code, item.response.id.clone(), - ))), + )), status: enums::AttemptStatus::Failure, ..item.data }) } else { Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.id.clone(), ), redirection_data: Box::new(None), @@ -3474,10 +3410,8 @@ impl } None => Ok(Self { status: item.data.status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -3499,11 +3433,9 @@ pub struct CybersourceRefundRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&CybersourceRouterData<&types::RefundsRouterData>> for CybersourceRefundRequest { +impl TryFrom<&CybersourceRouterData<&RefundsRouterData>> for CybersourceRefundRequest { type Error = error_stack::Report; - fn try_from( - item: &CybersourceRouterData<&types::RefundsRouterData>, - ) -> Result { + fn try_from(item: &CybersourceRouterData<&RefundsRouterData>) -> Result { Ok(Self { order_information: OrderInformation { amount_details: Amount { @@ -3551,24 +3483,24 @@ pub struct CybersourceRefundResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status.clone()); let response = if utils::is_refund_failure(refund_status) { - Err(types::ErrorResponse::foreign_from(( + Err(get_error_response( &item.response.error_information, &None, None, item.http_code, item.response.id.clone(), - ))) + )) } else { - Ok(types::RefundsResponseData { + Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status: enums::RefundStatus::from(item.response.status), }) @@ -3595,12 +3527,12 @@ pub struct CybersourceRsyncResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let response = match item .response @@ -3611,35 +3543,35 @@ impl TryFrom Ok(types::RefundsResponseData { + None => Ok(RefundsResponseData { connector_refund_id: item.response.id.clone(), refund_status: match item.data.response { Ok(response) => response.refund_status, @@ -3677,7 +3609,7 @@ pub struct CybersourceRecipientInfo { locality: String, administrative_area: Secret, postal_code: Secret, - country: api_enums::CountryAlpha2, + country: enums::CountryAlpha2, phone_number: Option>, } @@ -3720,12 +3652,12 @@ pub enum CybersourcePayoutBusinessType { } #[cfg(feature = "payouts")] -impl TryFrom<&CybersourceRouterData<&types::PayoutsRouterData>> +impl TryFrom<&CybersourceRouterData<&PayoutsRouterData>> for CybersourcePayoutFulfillRequest { type Error = error_stack::Report; fn try_from( - item: &CybersourceRouterData<&types::PayoutsRouterData>, + item: &CybersourceRouterData<&PayoutsRouterData>, ) -> Result { let payout_type = item.router_data.request.get_payout_type()?; match payout_type { @@ -3842,28 +3774,24 @@ pub enum CybersourcePayoutStatus { } #[cfg(feature = "payouts")] -impl ForeignFrom for enums::PayoutStatus { - fn foreign_from(status: CybersourcePayoutStatus) -> Self { - match status { - CybersourcePayoutStatus::Accepted => Self::Success, - CybersourcePayoutStatus::Declined | CybersourcePayoutStatus::InvalidRequest => { - Self::Failed - } +fn map_payout_status(status: CybersourcePayoutStatus) -> enums::PayoutStatus { + match status { + CybersourcePayoutStatus::Accepted => enums::PayoutStatus::Success, + CybersourcePayoutStatus::Declined | CybersourcePayoutStatus::InvalidRequest => { + enums::PayoutStatus::Failed } } } #[cfg(feature = "payouts")] -impl TryFrom> - for types::PayoutsRouterData -{ +impl TryFrom> for PayoutsRouterData { type Error = error_stack::Report; fn try_from( - item: types::PayoutsResponseRouterData, + item: PayoutsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::PayoutsResponseData { - status: Some(enums::PayoutStatus::foreign_from(item.response.status)), + response: Ok(PayoutsResponseData { + status: Some(map_payout_status(item.response.status)), connector_payout_id: Some(item.response.id), payout_eligible: None, should_add_next_step_to_process_tracker: false, @@ -3948,70 +3876,62 @@ pub struct AuthenticationErrorInformation { pub rmsg: String, } -impl - ForeignFrom<( - &Option, - &Option, - Option, - u16, - String, - )> for types::ErrorResponse -{ - fn foreign_from( - (error_data, risk_information, attempt_status, status_code, transaction_id): ( - &Option, - &Option, - Option, - u16, - String, - ), - ) -> Self { - let avs_message = risk_information - .clone() - .map(|client_risk_information| { - client_risk_information.rules.map(|rules| { - rules - .iter() - .map(|risk_info| { - risk_info.name.clone().map_or("".to_string(), |name| { - format!(" , {}", name.clone().expose()) - }) +pub fn get_error_response( + error_data: &Option, + risk_information: &Option, + attempt_status: Option, + status_code: u16, + transaction_id: String, +) -> ErrorResponse { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| { + risk_info.name.clone().map_or("".to_string(), |name| { + format!(" , {}", name.clone().expose()) }) - .collect::>() - .join("") - }) + }) + .collect::>() + .join("") }) - .unwrap_or(Some("".to_string())); + }) + .unwrap_or(Some("".to_string())); + + let detailed_error_info = error_data.as_ref().and_then(|error_data| { + error_data.details.as_ref().map(|details| { + details + .iter() + .map(|detail| format!("{} : {}", detail.field, detail.reason)) + .collect::>() + .join(", ") + }) + }); - let detailed_error_info = error_data - .clone() - .map(|error_data| match error_data.details { - Some(details) => details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", "), - None => "".to_string(), - }); + let reason = get_error_reason( + error_data + .as_ref() + .and_then(|error_info| error_info.message.clone()), + detailed_error_info, + avs_message, + ); - let reason = get_error_reason( - error_data.clone().and_then(|error_info| error_info.message), - detailed_error_info, - avs_message, - ); - let error_message = error_data.clone().and_then(|error_info| error_info.reason); - Self { - code: error_message - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code, - attempt_status, - connector_transaction_id: Some(transaction_id.clone()), - } + let error_message = error_data + .as_ref() + .and_then(|error_info| error_info.reason.clone()); + + ErrorResponse { + code: error_message + .clone() + .unwrap_or_else(|| hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message + .unwrap_or_else(|| hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status, + connector_transaction_id: Some(transaction_id), } } diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 3098d27e7e..f98d6843be 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -59,7 +59,7 @@ use crate::{ types::ResponseRouterData, utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, + RefundsRequestData, RouterData as ConnectorRouterData, }, }; @@ -131,7 +131,7 @@ impl ConnectorCommon for Deutschebank { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { @@ -311,18 +311,30 @@ impl ConnectorIntegration CustomResult { - if req.request.connector_mandate_id().is_none() { + let event_id = req.connector_request_reference_id.clone(); + let tx_action = if req.request.is_auto_capture()? { + "authorization" + } else { + "preauthorization" + }; + + if req.is_three_ds() && req.request.is_card() { + Ok(format!( + "{}/services/v2.1/headless3DSecure/event/{event_id}/{tx_action}/initialize", + self.base_url(connectors) + )) + } else if !req.is_three_ds() && req.request.is_card() { + Err(errors::ConnectorError::NotSupported { + message: "Non-ThreeDs".to_owned(), + connector: "deutschebank", + } + .into()) + } else if req.request.connector_mandate_id().is_none() { Ok(format!( "{}/services/v2.1/managedmandate", self.base_url(connectors) )) } else { - let event_id = req.connector_request_reference_id.clone(); - let tx_action = if req.request.is_auto_capture()? { - "authorization" - } else { - "preauthorization" - }; Ok(format!( "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}", self.base_url(connectors) @@ -375,7 +387,19 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - if data.request.connector_mandate_id().is_none() { + if data.is_three_ds() && data.request.is_card() { + let response: deutschebank::DeutschebankThreeDSInitializeResponse = res + .response + .parse_struct("DeutschebankPaymentsAuthorizeResponse") + .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, + }) + } else if data.request.connector_mandate_id().is_none() { let response: deutschebank::DeutschebankMandatePostResponse = res .response .parse_struct("DeutschebankMandatePostResponse") @@ -437,10 +461,18 @@ impl ConnectorIntegration), +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequest { + means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment, + tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data, + amount_total: DeutschebankThreeDSInitializeRequestAmountTotal, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestMeansOfPayment { + credit_card: DeutschebankThreeDSInitializeRequestCreditCard, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestCreditCard { + number: CardNumber, + expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry, + code: Secret, + cardholder: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry { + year: Secret, + month: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestAmountTotal { + amount: MinorUnit, + currency: api_models::enums::Currency, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestTds20Data { + communication_data: DeutschebankThreeDSInitializeRequestCommunicationData, + customer_data: DeutschebankThreeDSInitializeRequestCustomerData, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestCommunicationData { + method_notification_url: String, + cres_notification_url: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestCustomerData { + billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData, + cardholder_email: Email, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct DeutschebankThreeDSInitializeRequestCustomerBillingData { + street: Secret, + postal_code: Secret, + city: String, + state: Secret, + country: String, } impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> @@ -148,11 +218,9 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> None => { // 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, .. - }) => { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => { + if item.router_data.request.customer_acceptance.is_some() { let billing_address = item.router_data.get_billing_address()?; Ok(Self::MandatePost(DeutschebankMandatePostRequest { approval_by: DeutschebankSEPAApproval::Click, @@ -161,17 +229,60 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> first_name: billing_address.get_first_name()?.clone(), last_name: billing_address.get_last_name()?.clone(), })) + } else { + Err(errors::ConnectorError::MissingRequiredField { + field_name: "customer_acceptance", + } + .into()) } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) - .into()), } - } else { - Err(errors::ConnectorError::MissingRequiredField { - field_name: "customer_acceptance", + PaymentMethodData::Card(ccard) => { + if !item.router_data.clone().is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Non-ThreeDs".to_owned(), + connector: "deutschebank", + } + .into()) + } else { + let billing_address = item.router_data.get_billing_address()?; + Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest { + means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { + credit_card: DeutschebankThreeDSInitializeRequestCreditCard { + number: ccard.clone().card_number, + expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry { + year: ccard.get_expiry_year_4_digit(), + month: ccard.card_exp_month, + }, + code: ccard.card_cvc, + cardholder: item.router_data.get_billing_full_name()?, + }}, + amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { + amount: item.amount, + currency: item.router_data.request.currency, + }, + tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data { + communication_data: DeutschebankThreeDSInitializeRequestCommunicationData { + method_notification_url: item.router_data.request.get_complete_authorize_url()?, + cres_notification_url: item.router_data.request.get_complete_authorize_url()?, + }, + customer_data: DeutschebankThreeDSInitializeRequestCustomerData { + billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData { + street: billing_address.get_line1()?.clone(), + postal_code: billing_address.get_zip()?.clone(), + city: billing_address.get_city()?.to_string(), + state: billing_address.get_state()?.clone(), + country: item.router_data.get_billing_country()?.to_string(), + }, + cardholder_email: item.router_data.request.get_email()?, + } + } + }))) + } } - .into()) + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()), } } Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { @@ -209,6 +320,138 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponse { + outcome: DeutschebankThreeDSInitializeResponseOutcome, + challenge_required: Option, + processed: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponseProcessed { + rc: String, + message: String, + tx_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DeutschebankThreeDSInitializeResponseOutcome { + Processed, + ChallengeRequired, + MethodRequired, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponseChallengeRequired { + acs_url: String, + creq: String, +} + +impl + TryFrom< + ResponseRouterData< + Authorize, + DeutschebankThreeDSInitializeResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + Authorize, + DeutschebankThreeDSInitializeResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + match item.response.outcome { + DeutschebankThreeDSInitializeResponseOutcome::Processed => { + match item.response.processed { + Some(processed) => Ok(Self { + status: if is_response_success(&processed.rc) { + match item.data.request.is_auto_capture()? { + true => common_enums::AttemptStatus::Charged, + false => common_enums::AttemptStatus::Authorized, + } + } else { + common_enums::AttemptStatus::AuthenticationFailed + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + processed.tx_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(processed.tx_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + None => { + let response_string = format!("{:?}", item.response); + Err( + errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from( + response_string, + )) + .into(), + ) + } + } + } + DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => { + match item.response.challenge_required { + Some(challenge) => Ok(Self { + status: common_enums::AttemptStatus::AuthenticationPending, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(Some( + RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url: challenge.acs_url, + creq: challenge.creq, + }, + )), + 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 + }), + None => { + let response_string = format!("{:?}", item.response); + Err( + errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from( + response_string, + )) + .into(), + ) + } + } + } + DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: consts::NO_ERROR_CODE.to_owned(), + message: "METHOD_REQUIRED Flow not supported for deutschebank 3ds payments".to_owned(), + reason: Some("METHOD_REQUIRED Flow is not currently supported for deutschebank 3ds payments".to_owned()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }), + ..item.data + }), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum DeutschebankSEPAMandateStatus { @@ -450,79 +693,117 @@ pub struct DeutschebankDirectDebitRequest { mandate: DeutschebankMandate, } +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum DeutschebankCompleteAuthorizeRequest { + DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest), + DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest), +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankThreeDSCompleteAuthorizeRequest { + cres: String, +} + impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> - for DeutschebankDirectDebitRequest + for DeutschebankCompleteAuthorizeRequest { type Error = error_stack::Report; fn try_from( item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { - let account_holder = item.router_data.get_billing_address()?.get_full_name()?; - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - let queries_params = redirect_response - .params - .map(|param| { - let mut queries = HashMap::::new(); - let values = param.peek().split('&').collect::>(); - for value in values { - let pair = value.split('=').collect::>(); - queries.insert( - pair.first() - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), - pair.get(1) - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), + if matches!(item.router_data.payment_method, PaymentMethod::Card) { + let redirect_response_payload = item + .router_data + .request + .get_redirect_response_payload()? + .expose(); + + let cres = redirect_response_payload + .get("cres") + .and_then(|v| v.as_str()) + .map(String::from) + .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })?; + + Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest( + DeutschebankThreeDSCompleteAuthorizeRequest { cres }, + )) + } else { + match item.router_data.request.payment_method_data.clone() { + Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { + iban, .. + })) => { + let account_holder = item.router_data.get_billing_address()?.get_full_name()?; + let redirect_response = + item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let queries_params = redirect_response + .params + .map(|param| { + let mut queries = HashMap::::new(); + let values = param.peek().split('&').collect::>(); + for value in values { + let pair = value.split('=').collect::>(); + queries.insert( + pair.first() + .ok_or( + errors::ConnectorError::ResponseDeserializationFailed, + )? + .to_string(), + pair.get(1) + .ok_or( + errors::ConnectorError::ResponseDeserializationFailed, + )? + .to_string(), + ); + } + Ok::<_, errors::ConnectorError>(queries) + }) + .transpose()? + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + let reference = Secret::from( + queries_params + .get("reference") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "reference", + })? + .to_owned(), ); - } - Ok::<_, errors::ConnectorError>(queries) - }) - .transpose()? - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; - let reference = Secret::from( - queries_params - .get("reference") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "reference", - })? - .to_owned(), - ); - let signed_on = queries_params - .get("signed_on") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "signed_on", - })? - .to_owned(); - - match item.router_data.request.payment_method_data.clone() { - Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => { - Ok(Self { - amount_total: DeutschebankAmount { - amount: item.amount, - currency: item.router_data.request.currency, - }, - means_of_payment: DeutschebankMeansOfPayment { - bank_account: DeutschebankBankAccount { - account_holder, - iban: Secret::from(iban.peek().replace(" ", "")), + let signed_on = queries_params + .get("signed_on") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "signed_on", + })? + .to_owned(); + Ok(Self::DeutschebankDirectDebitRequest( + DeutschebankDirectDebitRequest { + amount_total: DeutschebankAmount { + amount: item.amount, + currency: item.router_data.request.currency, + }, + means_of_payment: DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount { + account_holder, + iban: Secret::from(iban.peek().replace(" ", "")), + }, + }, + mandate: { + DeutschebankMandate { + reference, + signed_on, + } + }, }, - }, - mandate: { - DeutschebankMandate { - reference, - signed_on, - } - }, - }) + )) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()), } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) - .into()), } } } @@ -636,6 +917,8 @@ impl #[serde(rename_all = "UPPERCASE")] pub enum DeutschebankTransactionKind { Directdebit, + #[serde(rename = "CREDITCARD_3DS20")] + Creditcard3ds20, } #[derive(Debug, Serialize, PartialEq)] @@ -649,10 +932,24 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for Deutscheba fn try_from( item: &DeutschebankRouterData<&PaymentsCaptureRouterData>, ) -> Result { - Ok(Self { - changed_amount: item.amount, - kind: DeutschebankTransactionKind::Directdebit, - }) + if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } @@ -772,10 +1069,21 @@ pub struct DeutschebankReversalRequest { impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest { type Error = error_stack::Report; - fn try_from(_item: &PaymentsCancelRouterData) -> Result { - Ok(Self { - kind: DeutschebankTransactionKind::Directdebit, - }) + fn try_from(item: &PaymentsCancelRouterData) -> Result { + if matches!(item.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) { + Ok(Self { + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } @@ -815,10 +1123,24 @@ pub struct DeutschebankRefundRequest { impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for DeutschebankRefundRequest { type Error = error_stack::Report; fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - changed_amount: item.amount.to_owned(), - kind: DeutschebankTransactionKind::Directdebit, - }) + if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 3ea8793d9f..70bca41582 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -254,16 +254,23 @@ impl ConnectorIntegration CustomResult { - let url = if req.request.off_session == Some(true) { - format!( + let optional_is_mit_flow = req.request.off_session; + let optional_is_nti_flow = req + .request + .mandate_id + .as_ref() + .map(|mandate_id| mandate_id.is_network_transaction_id_flow()); + let url = match (optional_is_mit_flow, optional_is_nti_flow) { + (Some(true), Some(false)) => 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) - ) + ), + _ => { + format!( + "{}RMS/API/Direct/1.4.0/index.php", + self.base_url(connectors) + ) + } }; Ok(url) } @@ -280,14 +287,24 @@ impl ConnectorIntegration { + let recurring_request = fiuu::FiuuMandateRequest::try_from(&connector_router_data)?; + build_form_from_struct(recurring_request) + .change_context(errors::ConnectorError::ParsingFailed)? + } + _ => { + let payment_request = fiuu::FiuuPaymentRequest::try_from(&connector_router_data)?; + build_form_from_struct(payment_request) + .change_context(errors::ConnectorError::ParsingFailed)? + } }; Ok(RequestContent::FormData(connector_req)) } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 14ce13f971..51fc23eee4 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -14,8 +14,8 @@ use common_utils::{ use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ payment_method_data::{ - BankRedirectData, Card, GooglePayWalletData, PaymentMethodData, RealTimePaymentData, - WalletData, + BankRedirectData, Card, CardDetailsForNetworkTransactionId, GooglePayWalletData, + PaymentMethodData, RealTimePaymentData, WalletData, }, router_data::{ ApplePayPredecryptData, ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData, @@ -287,6 +287,7 @@ pub struct FiuuPaymentRequest { pub enum FiuuPaymentMethodData { FiuuQRData(Box), FiuuCardData(Box), + FiuuCardWithNTI(Box), FiuuFpxData(Box), FiuuGooglePayData(Box), FiuuApplePayData(Box), @@ -322,6 +323,18 @@ pub struct FiuuCardData { customer_email: Option, } +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub struct FiuuCardWithNTI { + #[serde(rename = "TxnChannel")] + txn_channel: TxnChannel, + cc_pan: CardNumber, + cc_month: Secret, + cc_year: Secret, + #[serde(rename = "OriginalSchemeID")] + original_scheme_id: Secret, +} + #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub struct FiuuApplePayData { @@ -412,125 +425,150 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque 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(ref real_time_payment_data) => { - match *real_time_payment_data.clone() { - RealTimePaymentData::DuitNow {} => { - Ok(FiuuPaymentMethodData::FiuuQRData(Box::new(FiuuQRData { - txn_channel: TxnChannel::RppDuitNowQr, + let payment_method_data = match item + .router_data + .request + .mandate_id + .clone() + .and_then(|mandate_id| mandate_id.mandate_reference_id) + { + None => match item.router_data.request.payment_method_data { + PaymentMethodData::Card(ref card) => { + FiuuPaymentMethodData::try_from((card, item.router_data)) + } + PaymentMethodData::RealTimePayment(ref real_time_payment_data) => { + match *real_time_payment_data.clone() { + RealTimePaymentData::DuitNow {} => { + Ok(FiuuPaymentMethodData::FiuuQRData(Box::new(FiuuQRData { + txn_channel: TxnChannel::RppDuitNowQr, + }))) + } + RealTimePaymentData::Fps {} + | RealTimePaymentData::PromptPay {} + | RealTimePaymentData::VietQr {} => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()) + } + } + } + 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, }))) } - RealTimePaymentData::Fps {} - | RealTimePaymentData::PromptPay {} - | RealTimePaymentData::VietQr {} => { + 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::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 {} => { + }, + 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()) } }, - 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"))? - } + // Card payments using network transaction ID + Some(payments::MandateReferenceId::NetworkMandateId(network_transaction_id)) => { + match item.router_data.request.payment_method_data { + PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { + FiuuPaymentMethodData::try_from((raw_card_details, network_transaction_id)) } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), } - 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( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), }?; Ok(Self { @@ -556,7 +594,7 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { if item.request.is_customer_initiated_mandate_payment() { (Some(1), Some(item.get_billing_email()?)) } else { - (None, None) + (Some(3), None) }; let non_3ds = match item.is_three_ds() { false => 1, @@ -575,6 +613,21 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { } } +impl TryFrom<(&CardDetailsForNetworkTransactionId, String)> for FiuuPaymentMethodData { + type Error = Report; + fn try_from( + (raw_card_data, network_transaction_id): (&CardDetailsForNetworkTransactionId, String), + ) -> Result { + Ok(Self::FiuuCardWithNTI(Box::new(FiuuCardWithNTI { + txn_channel: TxnChannel::Creditan, + cc_pan: raw_card_data.card_number.clone(), + cc_month: raw_card_data.card_exp_month.clone(), + cc_year: raw_card_data.card_exp_year.clone(), + original_scheme_id: Secret::new(network_transaction_id), + }))) + } +} + impl TryFrom<&GooglePayWalletData> for FiuuPaymentMethodData { type Error = Report; fn try_from(data: &GooglePayWalletData) -> Result { @@ -838,11 +891,11 @@ impl reason: non_threeds_data.error_desc.clone(), status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(data.txn_id), }) } else { Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(data.txn_id), + resource_id: ResponseId::ConnectorTransactionId(data.txn_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -1041,14 +1094,14 @@ impl TryFrom> reason: refund_data.reason.clone(), status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(refund_data.refund_id.to_string()), }), ..item.data }) } else { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: refund_data.refund_id.to_string(), + connector_refund_id: refund_data.refund_id.clone().to_string(), refund_status, }), ..item.data @@ -1092,6 +1145,8 @@ pub struct FiuuPaymentSyncResponse { error_desc: String, #[serde(rename = "miscellaneous")] miscellaneous: Option>>, + #[serde(rename = "SchemeTransactionID")] + scheme_transaction_id: Option>, } #[derive(Debug, Serialize, Deserialize, Display, Clone, PartialEq)] @@ -1161,6 +1216,7 @@ impl TryFrom> for PaymentsSy FiuuPaymentResponse::FiuuPaymentSyncResponse(response) => { let stat_name = response.stat_name; let stat_code = response.stat_code.clone(); + let txn_id = response.tran_id; let status = enums::AttemptStatus::try_from(FiuuSyncStatus { stat_name, stat_code, @@ -1172,17 +1228,20 @@ impl TryFrom> for PaymentsSy message: response.error_desc.clone(), reason: Some(response.error_desc), attempt_status: Some(enums::AttemptStatus::Failure), - connector_transaction_id: None, + connector_transaction_id: Some(txn_id.clone()), }) } else { None }; let payments_response_data = PaymentsResponseData::TransactionResponse { - resource_id: item.data.request.connector_transaction_id.clone(), + resource_id: ResponseId::ConnectorTransactionId(txn_id.clone().to_string()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, - network_txn_id: None, + network_txn_id: response + .scheme_transaction_id + .as_ref() + .map(|id| id.clone().expose()), connector_response_reference_id: None, incremental_authorization_allowed: None, charge_id: None, @@ -1198,6 +1257,7 @@ impl TryFrom> for PaymentsSy capture_method: item.data.request.capture_method, status: response.status, })?; + let txn_id = response.tran_id; let mandate_reference = response.extra_parameters.as_ref().and_then(|extra_p| { let mandate_token: Result = serde_json::from_str(&extra_p.clone().expose()); match mandate_token { @@ -1233,13 +1293,13 @@ impl TryFrom> for PaymentsSy .unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()), reason: response.error_desc.clone(), attempt_status: Some(enums::AttemptStatus::Failure), - connector_transaction_id: None, + connector_transaction_id: Some(txn_id.clone()), }) } else { None }; let payments_response_data = PaymentsResponseData::TransactionResponse { - resource_id: item.data.request.connector_transaction_id.clone(), + resource_id: ResponseId::ConnectorTransactionId(txn_id.clone().to_string()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -1402,13 +1462,15 @@ impl TryFrom> .to_string(), ), attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(item.response.tran_id.clone()), }) } else { None }; let payments_response_data = PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.tran_id.to_string()), + resource_id: ResponseId::ConnectorTransactionId( + item.response.tran_id.clone().to_string(), + ), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -1513,13 +1575,15 @@ impl TryFrom> .to_string(), ), attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(item.response.tran_id.clone()), }) } else { None }; let payments_response_data = PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.tran_id.to_string()), + resource_id: ResponseId::ConnectorTransactionId( + item.response.tran_id.clone().to_string(), + ), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet.rs b/crates/hyperswitch_connectors/src/connectors/novalnet.rs index 99da9c5cac..0a47c8ccfb 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet.rs @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, }; use hyperswitch_interfaces::{ @@ -231,6 +231,79 @@ impl ConnectorIntegration impl ConnectorIntegration for Novalnet { + 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 { + let endpoint = self.base_url(connectors); + Ok(format!("{}/payment", endpoint)) + } + + fn get_request_body( + &self, + req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = novalnet::NovalnetPaymentsRequest::try_from(req)?; + 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: novalnet::NovalnetPaymentsResponse = res + .response + .parse_struct("Novalnet 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 Novalnet { diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 0a70393a13..6d3199ecf1 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -17,7 +17,7 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, }; use hyperswitch_interfaces::errors; @@ -28,8 +28,9 @@ use strum::Display; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, utils::{ - self, ApplePay, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, - PaymentsCaptureRequestData, PaymentsSyncRequestData, RefundsRequestData, RouterData as _, + self, AddressDetailsData, ApplePay, PaymentsAuthorizeRequestData, + PaymentsCancelRequestData, PaymentsCaptureRequestData, PaymentsSetupMandateRequestData, + PaymentsSyncRequestData, RefundsRequestData, RouterData as _, }, }; @@ -47,6 +48,19 @@ impl From<(StringMinorUnit, T)> for NovalnetRouterData { } } +const MINIMAL_CUSTOMER_DATA_PASSED: i64 = 1; +const CREATE_TOKEN_REQUIRED: i8 = 1; + +const TEST_MODE_ENABLED: i8 = 1; +const TEST_MODE_DISABLED: i8 = 0; + +fn get_test_mode(item: Option) -> i8 { + match item { + Some(true) => TEST_MODE_ENABLED, + Some(false) | None => TEST_MODE_DISABLED, + } +} + #[derive(Debug, Copy, Serialize, Deserialize, Clone)] pub enum NovalNetPaymentTypes { CREDITCARD, @@ -88,6 +102,13 @@ pub struct NovalnetCard { card_holder: Secret, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetRawCardDetails { + card_number: CardNumber, + card_expiry_month: Secret, + card_expiry_year: Secret, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct NovalnetMandate { token: Secret, @@ -107,6 +128,7 @@ pub struct NovalnetApplePay { #[serde(untagged)] pub enum NovalNetPaymentData { Card(NovalnetCard), + RawCardForNTI(NovalnetRawCardDetails), GooglePay(NovalnetGooglePay), ApplePay(NovalnetApplePay), MandatePayment(NovalnetMandate), @@ -117,11 +139,18 @@ pub struct NovalnetCustom { lang: String, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum NovalNetAmount { + StringMinor(StringMinorUnit), + Int(i64), +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct NovalnetPaymentsRequestTransaction { test_mode: i8, payment_type: NovalNetPaymentTypes, - amount: StringMinorUnit, + amount: NovalNetAmount, currency: common_enums::Currency, order_no: String, payment_data: Option, @@ -130,6 +159,7 @@ pub struct NovalnetPaymentsRequestTransaction { error_return_url: Option, enforce_3d: Option, //NOTE: Needed for CREDITCARD, GOOGLEPAY create_token: Option, + scheme_tid: Option>, // Card network's transaction ID } #[derive(Debug, Serialize, Clone)] @@ -171,10 +201,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym enums::AuthenticationType::ThreeDs => Some(1), enums::AuthenticationType::NoThreeDs => None, }; - let test_mode = match item.router_data.test_mode { - Some(true) => 1, - Some(false) | None => 0, - }; + let test_mode = get_test_mode(item.router_data.test_mode); let billing = NovalnetPaymentsRequestBilling { house_no: item.router_data.get_optional_billing_line1(), @@ -197,7 +224,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym mobile: item.router_data.get_optional_billing_phone_number(), billing: Some(billing), // no_nc is used to indicate if minimal customer data is passed or not - no_nc: 1, + no_nc: MINIMAL_CUSTOMER_DATA_PASSED, }; let lang = item @@ -209,7 +236,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym 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) + Some(CREATE_TOKEN_REQUIRED) } else { None }; @@ -234,7 +261,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::CREDITCARD, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -243,6 +270,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_card), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -265,7 +293,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::GOOGLEPAY, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -274,6 +302,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_google_pay), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -287,7 +316,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::APPLEPAY, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -299,6 +328,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym })), enforce_3d: None, create_token, + scheme_tid: None, }; Ok(Self { @@ -331,7 +361,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::PAYPAL, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -340,6 +370,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: None, enforce_3d: None, create_token, + scheme_tid: None, }; Ok(Self { merchant, @@ -389,7 +420,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -398,6 +429,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_mandate_data), enforce_3d, create_token: None, + scheme_tid: None, }; Ok(Self { @@ -407,6 +439,44 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym custom, }) } + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + network_transaction_id, + )) => match item.router_data.request.payment_method_data { + PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { + let novalnet_card = + NovalNetPaymentData::RawCardForNTI(NovalnetRawCardDetails { + card_number: raw_card_details.card_number.clone(), + card_expiry_month: raw_card_details.card_exp_month.clone(), + card_expiry_year: raw_card_details.card_exp_year.clone(), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::CREDITCARD, + amount: NovalNetAmount::StringMinor(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: Some(novalnet_card), + enforce_3d, + create_token, + scheme_tid: Some(network_transaction_id.into()), + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + _ => 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"), ) @@ -1380,3 +1450,199 @@ pub fn get_novalnet_dispute_status(status: WebhookEventType) -> WebhookDisputeSt pub fn option_to_result(opt: Option) -> Result { opt.ok_or(errors::ConnectorError::WebhookBodyDecodingFailed) } + +impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &SetupMandateRouterData) -> Result { + let auth = NovalnetAuthType::try_from(&item.connector_auth_type)?; + + let merchant = NovalnetPaymentsRequestMerchant { + signature: auth.product_activation_key, + tariff: auth.tariff_id, + }; + + let enforce_3d = match item.auth_type { + enums::AuthenticationType::ThreeDs => Some(1), + enums::AuthenticationType::NoThreeDs => None, + }; + let test_mode = get_test_mode(item.test_mode); + let req_address = item.get_billing_address()?.to_owned(); + + let billing = NovalnetPaymentsRequestBilling { + house_no: item.get_optional_billing_line1(), + street: item.get_optional_billing_line2(), + city: item.get_optional_billing_city().map(Secret::new), + zip: item.get_optional_billing_zip(), + country_code: item.get_optional_billing_country(), + }; + + let customer = NovalnetPaymentsRequestCustomer { + first_name: req_address.get_first_name()?.clone(), + last_name: req_address.get_last_name()?.clone(), + email: item.request.get_email()?.clone(), + mobile: item.get_optional_billing_phone_number(), + billing: Some(billing), + // no_nc is used to indicate if minimal customer data is passed or not + no_nc: MINIMAL_CUSTOMER_DATA_PASSED, + }; + + let lang = item + .request + .get_optional_language_from_browser_info() + .unwrap_or(consts::DEFAULT_LOCALE.to_string().to_string()); + + let custom = NovalnetCustom { lang }; + let hook_url = item.request.get_webhook_url()?; + let return_url = item.request.get_return_url()?; + let create_token = Some(CREATE_TOKEN_REQUIRED); + + match item.request.payment_method_data { + PaymentMethodData::Card(ref req_card) => { + 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: req_address.get_full_name()?.clone(), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::CREDITCARD, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.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: Some(novalnet_card), + enforce_3d, + create_token, + scheme_tid: None, + }; + + Ok(Self { + merchant, + transaction, + customer, + 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: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.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, + scheme_tid: None, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::ApplePay(payment_method_data) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::APPLEPAY, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.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: Secret::new(payment_method_data.payment_data.clone()), + })), + enforce_3d: None, + create_token, + scheme_tid: None, + }; + + 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"), + ))? + } + WalletDataPaymentMethod::PaypalRedirect(_) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::PAYPAL, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.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, + scheme_tid: None, + }; + + 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"), + ))? + } + }, + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ))?, + } + } +} diff --git a/crates/router/src/connector/wellsfargo.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo.rs similarity index 66% rename from crates/router/src/connector/wellsfargo.rs rename to crates/hyperswitch_connectors/src/connectors/wellsfargo.rs index b47b366225..8566ec7034 100644 --- a/crates/router/src/connector/wellsfargo.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo.rs @@ -1,42 +1,69 @@ pub mod transformers; use base64::Engine; +use common_enums::enums; use common_utils::{ - request::RequestContent, + consts, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use masking::{ExposeInterface, PeekInterface}; -use ring::{digest, hmac}; -use time::OffsetDateTime; -use transformers as wellsfargo; -use url::Url; - -use super::utils::convert_amount; -use crate::{ - configs::settings, - connector::{ - utils as connector_utils, - utils::{PaymentMethodDataType, RefundsRequestData}, +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + mandate_revoke::MandateRevoke, + payments::{ + Authorize, Capture, IncrementalAuthorization, 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, ConnectorSpecifications, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsSessionData, PaymentsSyncData, RefundsData, + SetupMandateRequestData, }, + router_response_types::{MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData}, types::{ + MandateRevokeRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData, + PaymentsCaptureRouterData, PaymentsIncrementalAuthorizationRouterData, + PaymentsSyncRouterData, RefundExecuteRouterData, RefundSyncRouterData, + SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignTryFrom, + refunds::{Refund, RefundExecute, RefundSync}, + ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + IncrementalAuthorizationType, MandateRevokeType, PaymentsAuthorizeType, + PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType, RefundSyncType, + Response, SetupMandateType, }, - utils::BytesExt, + webhooks, }; +use masking::{ExposeInterface, Mask, Maskable, PeekInterface}; +use ring::{digest, hmac}; +use time::OffsetDateTime; +use transformers as wellsfargo; +use url::Url; +use crate::{ + constants::{self, headers}, + types::ResponseRouterData, + utils::{self, convert_amount, PaymentMethodDataType, RefundsRequestData}, +}; #[derive(Clone)] pub struct Wellsfargo { amount_converter: &'static (dyn AmountConvertor + Sync), @@ -61,16 +88,16 @@ impl Wellsfargo { resource: &str, payload: &String, date: OffsetDateTime, - http_method: services::Method, + http_method: Method, ) -> CustomResult { let wellsfargo::WellsfargoAuthType { api_key, merchant_account, api_secret, } = auth; - let is_post_method = matches!(http_method, services::Method::Post); - let is_patch_method = matches!(http_method, services::Method::Patch); - let is_delete_method = matches!(http_method, services::Method::Delete); + let is_post_method = matches!(http_method, Method::Post); + let is_patch_method = matches!(http_method, Method::Patch); + let is_delete_method = matches!(http_method, Method::Delete); let digest_str = if is_post_method || is_patch_method { "digest " } else { @@ -116,7 +143,7 @@ impl ConnectorCommon for Wellsfargo { "application/json;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.wellsfargo.base_url.as_ref() } @@ -126,18 +153,18 @@ impl ConnectorCommon for Wellsfargo { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: Result< wellsfargo::WellsfargoErrorResponse, Report, > = res.response.parse_struct("Wellsfargo ErrorResponse"); let error_message = if res.status_code == 401 { - consts::CONNECTOR_UNAUTHORIZED_ERROR + constants::CONNECTOR_UNAUTHORIZED_ERROR } else { - consts::NO_ERROR_MESSAGE + hyperswitch_interfaces::consts::NO_ERROR_MESSAGE }; match response { Ok(transformers::WellsfargoErrorResponse::StandardError(response)) => { @@ -172,12 +199,10 @@ impl ConnectorCommon for Wellsfargo { .join(", ") }); ( - response - .reason - .clone() - .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { - reason.to_string() - }), + response.reason.clone().map_or( + hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), + |reason| reason.to_string(), + ), response .reason .map_or(error_message.to_string(), |reason| reason.to_string()), @@ -190,7 +215,7 @@ impl ConnectorCommon for Wellsfargo { } }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, code, message, @@ -202,9 +227,9 @@ impl ConnectorCommon for Wellsfargo { Ok(transformers::WellsfargoErrorResponse::AuthenticationError(response)) => { event_builder.map(|i| i.set_error_response_body(&response)); router_env::logger::info!(connector_response=?response); - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: response.response.rmsg.clone(), reason: Some(response.response.rmsg), attempt_status: None, @@ -226,9 +251,9 @@ impl ConnectorCommon for Wellsfargo { }) .collect::>() .join(" & "); - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: error_response.clone(), reason: Some(error_response), attempt_status: None, @@ -238,7 +263,7 @@ impl ConnectorCommon for Wellsfargo { 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}))); router_env::logger::error!(deserialization_error =? error_msg); - crate::utils::handle_json_response_deserialization_failure(res, "wellsfargo") + utils::handle_json_response_deserialization_failure(res, "wellsfargo") } } } @@ -257,21 +282,21 @@ impl ConnectorValidation for Wellsfargo { | 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()), + utils::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::Card, PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } @@ -281,9 +306,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let date = OffsetDateTime::now_utc(); let wellsfargo_req = self.get_request_body(req, connectors)?; let auth = wellsfargo::WellsfargoAuthType::try_from(&req.connector_auth_type)?; @@ -327,10 +352,7 @@ where ("Host".to_string(), host.to_string().into()), ("Signature".to_string(), signature.into_masked()), ]; - if matches!( - http_method, - services::Method::Post | services::Method::Put | services::Method::Patch - ) { + if matches!(http_method, Method::Post | Method::Put | Method::Patch) { headers.push(( "Digest".to_string(), format!("SHA-256={sha256}").into_masked(), @@ -351,28 +373,20 @@ impl api::ConnectorAccessToken for Wellsfargo {} impl api::PaymentToken for Wellsfargo {} impl api::ConnectorMandateRevoke for Wellsfargo {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Wellsfargo +impl ConnectorIntegration + for Wellsfargo { // Not Implemented (R) } -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Wellsfargo +impl ConnectorIntegration + for Wellsfargo { 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_content_type(&self) -> &'static str { @@ -380,15 +394,15 @@ impl } fn get_url( &self, - _req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &SetupMandateRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, + req: &SetupMandateRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = wellsfargo::WellsfargoZeroMandateRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -396,35 +410,33 @@ 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) - .url(&types::SetupMandateType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&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, - )?) + .headers(SetupMandateType::get_headers(self, req, connectors)?) + .set_body(SetupMandateType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &SetupMandateRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: wellsfargo::WellsfargoPaymentsResponse = res .response .parse_struct("WellsfargoSetupMandatesResponse") .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, @@ -433,17 +445,17 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: wellsfargo::WellsfargoServerErrorResponse = res .response .parse_struct("WellsfargoServerErrorResponse") @@ -459,76 +471,72 @@ impl }, None => None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl - ConnectorIntegration< - api::MandateRevoke, - types::MandateRevokeRequestData, - types::MandateRevokeResponseData, - > for Wellsfargo +impl ConnectorIntegration + for Wellsfargo { fn get_headers( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &MandateRevokeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_http_method(&self) -> services::Method { - services::Method::Delete + fn get_http_method(&self) -> Method { + Method::Delete } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, + req: &MandateRevokeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}tms/v1/paymentinstruments/{}", self.base_url(connectors), - connector_utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? + utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? )) } fn build_request( &self, - req: &types::MandateRevokeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &MandateRevokeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Delete) - .url(&types::MandateRevokeType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Delete) + .url(&MandateRevokeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::MandateRevokeType::get_headers( - self, req, connectors, - )?) + .headers(MandateRevokeType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::MandateRevokeRouterData, + data: &MandateRevokeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { if matches!(res.status_code, 204) { event_builder.map(|i| i.set_response_body(&serde_json::json!({"mandate_status": common_enums::MandateStatus::Revoked.to_string()}))); - Ok(types::MandateRevokeRouterData { - response: Ok(types::MandateRevokeResponseData { + Ok(MandateRevokeRouterData { + response: Ok(MandateRevokeResponseData { mandate_status: common_enums::MandateStatus::Revoked, }), ..data.clone() @@ -546,9 +554,9 @@ impl }); router_env::logger::info!(connector_response=?response_string); - Ok(types::MandateRevokeRouterData { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), + Ok(MandateRevokeRouterData { + response: Err(ErrorResponse { + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), message: response_string.clone(), reason: Some(response_string), status_code: res.status_code, @@ -561,33 +569,26 @@ impl } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { // Not Implemented (R) } impl api::PaymentSession for Wellsfargo {} -impl ConnectorIntegration - for Wellsfargo -{ -} +impl ConnectorIntegration for Wellsfargo {} -impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } @@ -597,8 +598,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -610,8 +611,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -627,18 +628,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(), @@ -646,11 +645,11 @@ impl ConnectorIntegration, - res: types::Response, + res: Response, ) -> CustomResult< - types::RouterData, + RouterData, errors::ConnectorError, > { let response: wellsfargo::WellsfargoPaymentsResponse = res @@ -659,7 +658,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: wellsfargo::WellsfargoServerErrorResponse = res .response .parse_struct("WellsfargoServerErrorResponse") @@ -686,38 +685,38 @@ impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } - fn get_http_method(&self) -> services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req .request @@ -737,31 +736,31 @@ 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: wellsfargo::WellsfargoTransactionResponse = res .response .parse_struct("Wellsfargo PaymentSyncResponse") .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, @@ -769,21 +768,19 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } @@ -793,8 +790,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}pts/v2/payments/", @@ -804,8 +801,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -821,18 +818,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) .set_body(self.get_request_body(req, connectors)?) .build(); @@ -841,17 +834,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: wellsfargo::WellsfargoPaymentsResponse = res .response .parse_struct("Wellsfargo 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, @@ -860,17 +853,17 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: wellsfargo::WellsfargoServerErrorResponse = res .response .parse_struct("WellsfargoServerErrorResponse") @@ -886,34 +879,34 @@ impl ConnectorIntegration None, }; - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, reason: response.status.clone(), - code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), message: response .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), attempt_status, connector_transaction_id: None, }) } } -impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -928,8 +921,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -953,15 +946,15 @@ 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)?) + RequestBuilder::new() + .method(Method::Post) + .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)?) .set_body(self.get_request_body(req, connectors)?) .build(), )) @@ -969,17 +962,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: wellsfargo::WellsfargoPaymentsResponse = res .response .parse_struct("Wellsfargo 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, @@ -988,17 +981,17 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } fn get_5xx_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: wellsfargo::WellsfargoServerErrorResponse = res .response .parse_struct("WellsfargoServerErrorResponse") @@ -1007,31 +1000,31 @@ impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } @@ -1041,8 +1034,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -1054,8 +1047,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -1069,17 +1062,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundExecuteRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) .set_body(self.get_request_body(req, connectors)?) .build(), )) @@ -1087,17 +1078,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: wellsfargo::WellsfargoRefundResponse = res .response .parse_struct("Wellsfargo 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, @@ -1105,33 +1096,31 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Wellsfargo -{ +impl ConnectorIntegration for Wellsfargo { 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) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } - fn get_http_method(&self) -> services::Method { - services::Method::Get + fn get_http_method(&self) -> Method { + Method::Get } fn get_url( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -1142,31 +1131,31 @@ 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: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: wellsfargo::WellsfargoRsyncResponse = res .response .parse_struct("Wellsfargo 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, @@ -1174,30 +1163,30 @@ impl ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration< - api::IncrementalAuthorization, - types::PaymentsIncrementalAuthorizationData, - types::PaymentsResponseData, + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, > for Wellsfargo { fn get_headers( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_http_method(&self) -> services::Method { - services::Method::Patch + fn get_http_method(&self) -> Method { + Method::Patch } fn get_content_type(&self) -> &'static str { @@ -1206,8 +1195,8 @@ impl fn get_url( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -1219,8 +1208,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - _connectors: &settings::Connectors, + req: &PaymentsIncrementalAuthorizationRouterData, + _connectors: &Connectors, ) -> CustomResult { let amount = convert_amount( self.amount_converter, @@ -1237,20 +1226,20 @@ impl } fn build_request( &self, - req: &types::PaymentsIncrementalAuthorizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsIncrementalAuthorizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Patch) - .url(&types::IncrementalAuthorizationType::get_url( + RequestBuilder::new() + .method(Method::Patch) + .url(&IncrementalAuthorizationType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::IncrementalAuthorizationType::get_headers( + .headers(IncrementalAuthorizationType::get_headers( self, req, connectors, )?) - .set_body(types::IncrementalAuthorizationType::get_request_body( + .set_body(IncrementalAuthorizationType::get_request_body( self, req, connectors, )?) .build(), @@ -1258,14 +1247,14 @@ impl } fn handle_response( &self, - data: &types::PaymentsIncrementalAuthorizationRouterData, + data: &PaymentsIncrementalAuthorizationRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, + res: Response, ) -> CustomResult< - types::RouterData< - api::IncrementalAuthorization, - types::PaymentsIncrementalAuthorizationData, - types::PaymentsResponseData, + RouterData< + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, >, errors::ConnectorError, > { @@ -1275,44 +1264,41 @@ impl .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; 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, - }, - true, - )) + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } #[async_trait::async_trait] -impl api::IncomingWebhook for Wellsfargo { +impl webhooks::IncomingWebhook for Wellsfargo { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs similarity index 72% rename from crates/router/src/connector/wellsfargo/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index 937af33bd7..6ce9a4d7aa 100644 --- a/crates/router/src/connector/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -1,30 +1,47 @@ use api_models::payments; use base64::Engine; -use common_enums::FutureUsage; +use common_enums::{enums, FutureUsage}; use common_utils::{ - pii, + consts, pii, types::{SemanticVersion, StringMajorUnit}, }; +use hyperswitch_domain_models::{ + payment_method_data::{ + ApplePayWalletData, BankDebitData, GooglePayWalletData, PaymentMethodData, WalletData, + }, + router_data::{ + AdditionalPaymentMethodConnectorResponse, ApplePayPredecryptData, ConnectorAuthType, + ConnectorResponseData, ErrorResponse, PaymentMethodToken, RouterData, + }, + router_flow_types::{ + payments::Authorize, + refunds::{Execute, RSync}, + SetupMandate, + }, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, + ResponseId, SetupMandateRequestData, + }, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsIncrementalAuthorizationRouterData, RefundsRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{api, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::{ - connector::utils::{ + constants, + types::{RefundsResponseRouterData, ResponseRouterData}, + unimplemented_payment_method, + utils::{ self, AddressDetailsData, ApplePayDecrypt, CardData, PaymentsAuthorizeRequestData, - PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RecurringMandateData, RouterData, + PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RecurringMandateData, + RouterData as OtherRouterData, }, - consts, - core::errors, - types::{ - self, - api::{self, enums as api_enums}, - domain, - storage::enums, - transformers::{ForeignFrom, ForeignTryFrom}, - ApplePayPredecryptData, - }, - unimplemented_payment_method, }; #[derive(Debug, Serialize)] @@ -51,9 +68,9 @@ pub struct WellsfargoZeroMandateRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { +impl TryFrom<&SetupMandateRouterData> for WellsfargoZeroMandateRequest { type Error = error_stack::Report; - fn try_from(item: &types::SetupMandateRouterData) -> Result { + fn try_from(item: &SetupMandateRouterData) -> Result { let email = item.request.get_email()?; let bill_to = build_bill_to(item.get_optional_billing(), email)?; @@ -85,7 +102,7 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { }; let (payment_information, solution) = match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { let card_issuer = ccard.get_card_issuer(); let card_type = match card_issuer { Ok(issuer) => Some(String::from(issuer)), @@ -105,55 +122,54 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { ) } - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { - match item.payment_method_token.clone() { - Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let expiration_month = decrypt_data.get_expiry_month()?; - let expiration_year = decrypt_data.get_four_digit_expiry_year()?; - ( - PaymentInformation::ApplePay(Box::new( - ApplePayPaymentInformation { - tokenized_card: TokenizedCard { - number: decrypt_data - .application_primary_account_number, - cryptogram: decrypt_data - .payment_data - .online_payment_cryptogram, - transaction_type: TransactionType::ApplePay, - expiration_year, - expiration_month, - }, + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => match item.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + let expiration_month = decrypt_data.get_expiry_month()?; + let expiration_year = decrypt_data.get_four_digit_expiry_year()?; + ( + PaymentInformation::ApplePay(Box::new( + ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: decrypt_data.application_primary_account_number, + cryptogram: decrypt_data + .payment_data + .online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, }, - )), - Some(PaymentSolution::ApplePay), - ) - } - 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( - ApplePayTokenPaymentInformation { - fluid_data: FluidData { - value: Secret::from(apple_pay_data.payment_data), - descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), - }, - tokenized_card: ApplePayTokenizedCard { - transaction_type: TransactionType::ApplePay, }, + )), + Some(PaymentSolution::ApplePay), + ) + } + PaymentMethodToken::Token(_) => Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Wellsfargo" + ))?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? + } + }, + None => ( + PaymentInformation::ApplePayToken(Box::new( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + descriptor: Some(FLUID_DATA_DESCRIPTOR.to_string()), }, - )), - Some(PaymentSolution::ApplePay), - ), - } - } - domain::WalletData::GooglePay(google_pay_data) => ( + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + )), + Some(PaymentSolution::ApplePay), + ), + }, + WalletData::GooglePay(google_pay_data) => ( PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { fluid_data: FluidData { value: Secret::from( @@ -165,52 +181,52 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { })), Some(PaymentSolution::GooglePay), ), - 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::Paze(_) - | 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::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("Wellsfargo"), ))?, }, - 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::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + 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("Wellsfargo"), ))? @@ -515,15 +531,13 @@ pub struct BillTo { administrative_area: Option>, #[serde(skip_serializing_if = "Option::is_none")] postal_code: Option>, - country: Option, + country: Option, email: pii::Email, phone_number: Option>, } -impl From<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> - for ClientReferenceInformation -{ - fn from(item: &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { +impl From<&WellsfargoRouterData<&PaymentsAuthorizeRouterData>> for ClientReferenceInformation { + fn from(item: &WellsfargoRouterData<&PaymentsAuthorizeRouterData>) -> Self { Self { code: Some(item.router_data.connector_request_reference_id.clone()), } @@ -532,7 +546,7 @@ impl From<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> impl TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Option, Option, )> for ProcessingInformation @@ -540,7 +554,7 @@ impl type Error = error_stack::Report; fn try_from( (item, solution, network): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Option, Option, ), @@ -567,18 +581,15 @@ impl .router_data .request .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, FutureUsage::OffSession) - }) + == Some(FutureUsage::OffSession) && (item.router_data.request.customer_acceptance.is_some() || item .router_data .request .setup_mandate_details .clone() - .map_or(false, |mandate_details| { - mandate_details.customer_acceptance.is_some() - })) { + .is_some_and(|mandate_details| mandate_details.customer_acceptance.is_some())) + { ( Some(vec![WellsfargoActionsList::TokenCreate]), Some(vec![ @@ -784,13 +795,13 @@ fn get_commerce_indicator_for_external_authentication( impl From<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Option, )> for OrderInformationWithBill { fn from( (item, bill_to): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Option, ), ) -> Self { @@ -853,35 +864,32 @@ fn build_bill_to( ad } -impl ForeignFrom for Vec { - fn foreign_from(metadata: Value) -> Self { - let hashmap: std::collections::BTreeMap = - serde_json::from_str(&metadata.to_string()) - .unwrap_or(std::collections::BTreeMap::new()); - let mut vector: Self = Self::new(); - let mut iter = 1; - for (key, value) in hashmap { - vector.push(MerchantDefinedInformation { - key: iter, - value: format!("{key}={value}"), - }); - iter += 1; - } - vector +fn convert_metadata_to_merchant_defined_info(metadata: Value) -> Vec { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()).unwrap_or(std::collections::BTreeMap::new()); + let mut vector = Vec::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; } + vector } impl TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, )> for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::Card, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -911,7 +919,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let consumer_authentication_information = item .router_data @@ -953,17 +961,17 @@ impl impl TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, )> for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, apple_pay_data, apple_pay_wallet_data): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, Box, - domain::ApplePayWalletData, + ApplePayWalletData, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -992,7 +1000,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); let ucaf_collection_indicator = match apple_pay_wallet_data .payment_method .network @@ -1024,15 +1032,15 @@ impl impl TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, )> for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, google_pay_data): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::GooglePayWalletData, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + GooglePayWalletData, ), ) -> Result { let email = item.router_data.request.get_email()?; @@ -1056,7 +1064,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, @@ -1087,22 +1095,22 @@ impl TryFrom> for AccountType { impl TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::BankDebitData, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + BankDebitData, )> for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, bank_debit_data): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - domain::BankDebitData, + &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, + BankDebitData, ), ) -> Result { let email = 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 payment_information = match bank_debit_data { - domain::BankDebitData::AchBankDebit { + BankDebitData::AchBankDebit { account_number, routing_number, bank_type, @@ -1118,13 +1126,11 @@ impl }, }, ))), - domain::BankDebitData::SepaBankDebit { .. } - | domain::BankDebitData::BacsBankDebit { .. } - | domain::BankDebitData::BecsBankDebit { .. } => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Wellsfargo"), - )) - } + BankDebitData::SepaBankDebit { .. } + | BankDebitData::BacsBankDebit { .. } + | BankDebitData::BecsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wellsfargo"), + )), }?; let processing_information = ProcessingInformation::try_from((item, Some(PaymentSolution::GooglePay), None))?; @@ -1140,33 +1146,31 @@ impl } } -impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> - for WellsfargoPaymentsRequest -{ +impl TryFrom<&WellsfargoRouterData<&PaymentsAuthorizeRouterData>> for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( - item: &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, + item: &WellsfargoRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.connector_mandate_id() { Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), None => { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::ApplePay(apple_pay_data) => { + PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::ApplePay(apple_pay_data) => { match item.router_data.payment_method_token.clone() { Some(payment_method_token) => match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { Self::try_from((item, decrypt_data, apple_pay_data)) } - types::PaymentMethodToken::Token(_) => { + PaymentMethodToken::Token(_) => { Err(unimplemented_payment_method!( "Apple Pay", "Manual", "Wellsfargo" ))? } - types::PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? } }, @@ -1199,9 +1203,7 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> ); let merchant_defined_information = item.router_data.request.metadata.clone().map(|metadata| { - Vec::::foreign_from( - metadata, - ) + convert_metadata_to_merchant_defined_info(metadata) }); let ucaf_collection_indicator = match apple_pay_data .payment_method @@ -1234,44 +1236,42 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> } } } - domain::WalletData::GooglePay(google_pay_data) => { + WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } - 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::Paze(_) - | 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( - utils::get_unimplemented_payment_method_error_message("Wellsfargo"), - ) - .into()) - } + 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("Wellsfargo"), + ) + .into()), }, // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. // This is a fallback implementation in the event of catastrophe. - domain::PaymentMethodData::MandatePayment => { + PaymentMethodData::MandatePayment => { let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or( errors::ConnectorError::MissingRequiredField { @@ -1280,24 +1280,22 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> )?; Self::try_from((item, connector_mandate_id)) } - domain::PaymentMethodData::BankDebit(bank_debit) => { - Self::try_from((item, bank_debit)) - } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankTransfer(_) - | 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::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::BankDebit(bank_debit) => Self::try_from((item, bank_debit)), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | 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("Wellsfargo"), ) @@ -1309,18 +1307,12 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl - TryFrom<( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - String, - )> for WellsfargoPaymentsRequest +impl TryFrom<(&WellsfargoRouterData<&PaymentsAuthorizeRouterData>, String)> + for WellsfargoPaymentsRequest { type Error = error_stack::Report; fn try_from( - (item, connector_mandate_id): ( - &WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>, - String, - ), + (item, connector_mandate_id): (&WellsfargoRouterData<&PaymentsAuthorizeRouterData>, String), ) -> Result { let processing_information = ProcessingInformation::try_from((item, None, None))?; let payment_instrument = WellsfargoPaymentInstrument { @@ -1341,7 +1333,7 @@ impl .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information, payment_information, @@ -1370,19 +1362,19 @@ pub struct WellsfargoPaymentsIncrementalAuthorizationRequest { order_information: OrderInformationIncrementalAuthorization, } -impl TryFrom<&WellsfargoRouterData<&types::PaymentsCaptureRouterData>> +impl TryFrom<&WellsfargoRouterData<&PaymentsCaptureRouterData>> for WellsfargoPaymentsCaptureRequest { type Error = error_stack::Report; fn try_from( - item: &WellsfargoRouterData<&types::PaymentsCaptureRouterData>, + item: &WellsfargoRouterData<&PaymentsCaptureRouterData>, ) -> Result { let merchant_defined_information = item .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { processing_information: ProcessingInformation { capture_options: Some(CaptureOptions { @@ -1411,12 +1403,12 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsCaptureRouterData>> } } -impl TryFrom<&WellsfargoRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> +impl TryFrom<&WellsfargoRouterData<&PaymentsIncrementalAuthorizationRouterData>> for WellsfargoPaymentsIncrementalAuthorizationRequest { type Error = error_stack::Report; fn try_from( - item: &WellsfargoRouterData<&types::PaymentsIncrementalAuthorizationRouterData>, + item: &WellsfargoRouterData<&PaymentsIncrementalAuthorizationRouterData>, ) -> Result { Ok(Self { processing_information: ProcessingInformation { @@ -1466,17 +1458,17 @@ pub struct ReversalInformation { reason: String, } -impl TryFrom<&WellsfargoRouterData<&types::PaymentsCancelRouterData>> for WellsfargoVoidRequest { +impl TryFrom<&WellsfargoRouterData<&PaymentsCancelRouterData>> for WellsfargoVoidRequest { type Error = error_stack::Report; fn try_from( - value: &WellsfargoRouterData<&types::PaymentsCancelRouterData>, + value: &WellsfargoRouterData<&PaymentsCancelRouterData>, ) -> Result { let merchant_defined_information = value .router_data .request .metadata .clone() - .map(Vec::::foreign_from); + .map(convert_metadata_to_merchant_defined_info); Ok(Self { client_reference_information: ClientReferenceInformation { code: Some(value.router_data.connector_request_reference_id.clone()), @@ -1510,10 +1502,10 @@ pub struct WellsfargoAuthType { pub(super) api_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for WellsfargoAuthType { +impl TryFrom<&ConnectorAuthType> for WellsfargoAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -1563,43 +1555,42 @@ pub enum WellsfargoIncrementalAuthorizationStatus { AuthorizedPendingReview, } -impl ForeignFrom<(WellsfargoPaymentStatus, bool)> for enums::AttemptStatus { - fn foreign_from((status, capture): (WellsfargoPaymentStatus, bool)) -> Self { - match status { - WellsfargoPaymentStatus::Authorized - | WellsfargoPaymentStatus::AuthorizedPendingReview => { - if capture { - // Because Wellsfargo will return Payment Status as Authorized even in AutoCapture Payment - Self::Charged - } else { - Self::Authorized - } - } - WellsfargoPaymentStatus::Pending => { - if capture { - Self::Charged - } else { - Self::Pending - } +fn map_attempt_status(status: WellsfargoPaymentStatus, capture: bool) -> enums::AttemptStatus { + match status { + WellsfargoPaymentStatus::Authorized | WellsfargoPaymentStatus::AuthorizedPendingReview => { + if capture { + // Because Wellsfargo will return Payment Status as Authorized even in AutoCapture Payment + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } - WellsfargoPaymentStatus::Succeeded | WellsfargoPaymentStatus::Transmitted => { - Self::Charged + } + WellsfargoPaymentStatus::Pending => { + if capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Pending } - WellsfargoPaymentStatus::Voided - | WellsfargoPaymentStatus::Reversed - | WellsfargoPaymentStatus::Cancelled => Self::Voided, - WellsfargoPaymentStatus::Failed - | WellsfargoPaymentStatus::Declined - | WellsfargoPaymentStatus::AuthorizedRiskDeclined - | WellsfargoPaymentStatus::Rejected - | WellsfargoPaymentStatus::InvalidRequest - | WellsfargoPaymentStatus::ServerError => Self::Failure, - WellsfargoPaymentStatus::PendingAuthentication => Self::AuthenticationPending, - WellsfargoPaymentStatus::PendingReview - | WellsfargoPaymentStatus::StatusNotReceived - | WellsfargoPaymentStatus::Challenge - | WellsfargoPaymentStatus::Accepted => Self::Pending, } + WellsfargoPaymentStatus::Succeeded | WellsfargoPaymentStatus::Transmitted => { + enums::AttemptStatus::Charged + } + WellsfargoPaymentStatus::Voided + | WellsfargoPaymentStatus::Reversed + | WellsfargoPaymentStatus::Cancelled => enums::AttemptStatus::Voided, + WellsfargoPaymentStatus::Failed + | WellsfargoPaymentStatus::Declined + | WellsfargoPaymentStatus::AuthorizedRiskDeclined + | WellsfargoPaymentStatus::Rejected + | WellsfargoPaymentStatus::InvalidRequest + | WellsfargoPaymentStatus::ServerError => enums::AttemptStatus::Failure, + WellsfargoPaymentStatus::PendingAuthentication => { + enums::AttemptStatus::AuthenticationPending + } + WellsfargoPaymentStatus::PendingReview + | WellsfargoPaymentStatus::StatusNotReceived + | WellsfargoPaymentStatus::Challenge + | WellsfargoPaymentStatus::Accepted => enums::AttemptStatus::Pending, } } @@ -1691,84 +1682,17 @@ pub struct WellsfargoErrorInformation { details: Option>, } -impl - ForeignFrom<( - &WellsfargoErrorInformationResponse, - types::ResponseRouterData, - Option, - )> for types::RouterData -{ - fn foreign_from( - (error_response, item, transaction_status): ( - &WellsfargoErrorInformationResponse, - types::ResponseRouterData< - F, - WellsfargoPaymentsResponse, - T, - types::PaymentsResponseData, - >, - Option, - ), - ) -> Self { - let detailed_error_info = - error_response - .error_information - .details - .to_owned() - .map(|details| { - details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", ") - }); - - let reason = get_error_reason( - error_response.error_information.message.clone(), - detailed_error_info, - None, - ); - let response = Err(types::ErrorResponse { - code: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_response - .error_information - .reason - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(error_response.id.clone()), - }); - match transaction_status { - Some(status) => Self { - response, - status, - ..item.data - }, - None => Self { - response, - ..item.data - }, - } - } -} - fn get_error_response_if_failure( (info_response, status, http_code): (&WellsfargoPaymentsResponse, enums::AttemptStatus, u16), -) -> Option { +) -> Option { if utils::is_payment_failure(status) { - Some(types::ErrorResponse::foreign_from(( + Some(get_error_response( &info_response.error_information, &info_response.risk_information, Some(status), http_code, info_response.id.clone(), - ))) + )) } else { None } @@ -1776,7 +1700,7 @@ fn get_error_response_if_failure( fn get_payment_response( (info_response, status, http_code): (&WellsfargoPaymentsResponse, enums::AttemptStatus, u16), -) -> Result { +) -> Result { let error_response = get_error_response_if_failure((info_response, status, http_code)); match error_response { Some(error) => Err(error), @@ -1787,7 +1711,7 @@ fn get_payment_response( info_response .token_information .clone() - .map(|token_info| types::MandateReference { + .map(|token_info| MandateReference { connector_mandate_id: token_info .payment_instrument .map(|payment_instrument| payment_instrument.id.expose()), @@ -1796,8 +1720,8 @@ fn get_payment_response( connector_mandate_request_reference_id: None, }); - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(info_response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -1820,38 +1744,37 @@ fn get_payment_response( impl TryFrom< - types::ResponseRouterData< - api::Authorize, + ResponseRouterData< + Authorize, WellsfargoPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, - > - for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::Authorize, + item: ResponseRouterData< + Authorize, WellsfargoPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_attempt_status( item.response .status .clone() .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), item.data.request.is_auto_capture()?, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); let connector_response = item .response .processor_information .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + .map(AdditionalPaymentMethodConnectorResponse::from) + .map(ConnectorResponseData::with_additional_payment_method_data); Ok(Self { status, @@ -1862,7 +1785,7 @@ impl } } -impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnectorResponse { +impl From<&ClientProcessorInformation> for AdditionalPaymentMethodConnectorResponse { fn from(processor_information: &ClientProcessorInformation) -> Self { let payment_checks = Some( serde_json::json!({"avs_response": processor_information.avs, "card_verification": processor_information.card_verification}), @@ -1877,30 +1800,30 @@ impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnect impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, WellsfargoPaymentsResponse, - 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, WellsfargoPaymentsResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_attempt_status( item.response .status .clone() .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), true, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); Ok(Self { status, @@ -1912,30 +1835,25 @@ impl impl TryFrom< - types::ResponseRouterData< - F, - WellsfargoPaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, - >, - > for types::RouterData + ResponseRouterData, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, WellsfargoPaymentsResponse, - types::PaymentsCancelData, - types::PaymentsResponseData, + PaymentsCancelData, + PaymentsResponseData, >, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( + let status = map_attempt_status( item.response .status .clone() .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), false, - )); + ); let response = get_payment_response((&item.response, status, item.http_code)); Ok(Self { status, @@ -1948,33 +1866,28 @@ impl // zero dollar response impl TryFrom< - types::ResponseRouterData< - api::SetupMandate, + ResponseRouterData< + SetupMandate, WellsfargoPaymentsResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, - > - for types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::SetupMandate, + item: ResponseRouterData< + SetupMandate, WellsfargoPaymentsResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, ) -> Result { let mandate_reference = item.response .token_information .clone() - .map(|token_info| types::MandateReference { + .map(|token_info| MandateReference { connector_mandate_id: token_info .payment_instrument .map(|payment_instrument| payment_instrument.id.expose()), @@ -1982,13 +1895,13 @@ impl mandate_metadata: None, connector_mandate_request_reference_id: None, }); - let mut mandate_status = enums::AttemptStatus::foreign_from(( + let mut mandate_status = map_attempt_status( item.response .status .clone() .unwrap_or(WellsfargoPaymentStatus::StatusNotReceived), false, - )); + ); if matches!(mandate_status, enums::AttemptStatus::Authorized) { //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. mandate_status = enums::AttemptStatus::Charged @@ -2000,17 +1913,15 @@ impl .response .processor_information .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + .map(AdditionalPaymentMethodConnectorResponse::from) + .map(ConnectorResponseData::with_additional_payment_method_data); Ok(Self { status: mandate_status, response: match error_response { Some(error) => Err(error), - None => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), + None => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -2040,47 +1951,38 @@ impl } impl - ForeignTryFrom<( - types::ResponseRouterData< + TryFrom< + ResponseRouterData< F, WellsfargoPaymentsIncrementalAuthorizationResponse, T, - types::PaymentsResponseData, + PaymentsResponseData, >, - bool, - )> for types::RouterData + > for RouterData { type Error = error_stack::Report; - fn foreign_try_from( - data: ( - types::ResponseRouterData< - F, - WellsfargoPaymentsIncrementalAuthorizationResponse, - T, - types::PaymentsResponseData, - >, - bool, - ), + fn try_from( + item: ResponseRouterData< + F, + WellsfargoPaymentsIncrementalAuthorizationResponse, + T, + PaymentsResponseData, + >, ) -> Result { - let item = data.0; Ok(Self { response: match item.response.error_information { - Some(error) => Ok( - types::PaymentsResponseData::IncrementalAuthorizationResponse { - status: common_enums::AuthorizationStatus::Failure, - error_code: error.reason, - error_message: error.message, - connector_authorization_id: None, - }, - ), - _ => Ok( - types::PaymentsResponseData::IncrementalAuthorizationResponse { - status: item.response.status.into(), - error_code: None, - error_message: None, - connector_authorization_id: None, - }, - ), + Some(error) => Ok(PaymentsResponseData::IncrementalAuthorizationResponse { + status: common_enums::AuthorizationStatus::Failure, + error_code: error.reason, + error_message: error.message, + connector_authorization_id: None, + }), + _ => Ok(PaymentsResponseData::IncrementalAuthorizationResponse { + status: item.response.status.into(), + error_code: None, + error_message: None, + connector_authorization_id: None, + }), }, ..item.data }) @@ -2104,49 +2006,46 @@ pub struct ApplicationInformation { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, WellsfargoTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, WellsfargoTransactionResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, ) -> Result { match item.response.application_information.status { Some(status) => { - let status = enums::AttemptStatus::foreign_from(( - status, - item.data.request.is_auto_capture()?, - )); + let status = map_attempt_status(status, item.data.request.is_auto_capture()?); let incremental_authorization_allowed = Some(status == enums::AttemptStatus::Authorized); let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { - response: Err(types::ErrorResponse::foreign_from(( + response: Err(get_error_response( &item.response.error_information, &risk_info, Some(status), item.http_code, item.response.id.clone(), - ))), + )), status: enums::AttemptStatus::Failure, ..item.data }) } else { Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.id.clone(), ), redirection_data: Box::new(None), @@ -2167,10 +2066,8 @@ impl } None => Ok(Self { status: item.data.status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -2192,11 +2089,9 @@ pub struct WellsfargoRefundRequest { client_reference_information: ClientReferenceInformation, } -impl TryFrom<&WellsfargoRouterData<&types::RefundsRouterData>> for WellsfargoRefundRequest { +impl TryFrom<&WellsfargoRouterData<&RefundsRouterData>> for WellsfargoRefundRequest { type Error = error_stack::Report; - fn try_from( - item: &WellsfargoRouterData<&types::RefundsRouterData>, - ) -> Result { + fn try_from(item: &WellsfargoRouterData<&RefundsRouterData>) -> Result { Ok(Self { order_information: OrderInformation { amount_details: Amount { @@ -2244,24 +2139,24 @@ pub struct WellsfargoRefundResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status.clone()); let response = if utils::is_refund_failure(refund_status) { - Err(types::ErrorResponse::foreign_from(( + Err(get_error_response( &item.response.error_information, &None, None, item.http_code, item.response.id.clone(), - ))) + )) } else { - Ok(types::RefundsResponseData { + Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status: enums::RefundStatus::from(item.response.status), }) @@ -2288,12 +2183,12 @@ pub struct WellsfargoRsyncResponse { error_information: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let response = match item .response @@ -2304,35 +2199,35 @@ impl TryFrom Ok(types::RefundsResponseData { + None => Ok(RefundsResponseData { connector_refund_id: item.response.id.clone(), refund_status: match item.data.response { Ok(response) => response.refund_status, @@ -2421,73 +2316,60 @@ pub struct AuthenticationErrorInformation { pub rmsg: String, } -impl - ForeignFrom<( - &Option, - &Option, - Option, - u16, - String, - )> for types::ErrorResponse -{ - fn foreign_from( - (error_data, risk_information, attempt_status, status_code, transaction_id): ( - &Option, - &Option, - Option, - u16, - String, - ), - ) -> Self { - let avs_message = risk_information - .clone() - .map(|client_risk_information| { - client_risk_information.rules.map(|rules| { - rules - .iter() - .map(|risk_info| { - risk_info.name.clone().map_or("".to_string(), |name| { - format!(" , {}", name.clone().expose()) - }) +pub fn get_error_response( + error_data: &Option, + risk_information: &Option, + attempt_status: Option, + status_code: u16, + transaction_id: String, +) -> ErrorResponse { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| { + risk_info.name.clone().map_or("".to_string(), |name| { + format!(" , {}", name.clone().expose()) }) - .collect::>() - .join("") - }) + }) + .collect::>() + .join("") }) - .unwrap_or(Some("".to_string())); + }) + .unwrap_or(Some("".to_string())); + + let detailed_error_info = error_data + .clone() + .map(|error_data| match error_data.details { + Some(details) => details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", "), + None => "".to_string(), + }); - let detailed_error_info = error_data + let reason = get_error_reason( + error_data.clone().and_then(|error_info| error_info.message), + detailed_error_info, + avs_message, + ); + let error_message = error_data.clone().and_then(|error_info| error_info.reason); + ErrorResponse { + code: error_message .clone() - .map(|error_data| match error_data.details { - Some(details) => details - .iter() - .map(|details| format!("{} : {}", details.field, details.reason)) - .collect::>() - .join(", "), - None => "".to_string(), - }); - - let reason = get_error_reason( - error_data.clone().and_then(|error_info| error_info.message), - detailed_error_info, - avs_message, - ); - let error_message = error_data.clone().and_then(|error_info| error_info.reason); - Self { - code: error_message - .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), - message: error_message - .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason, - status_code, - attempt_status, - connector_transaction_id: Some(transaction_id.clone()), - } + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status, + connector_transaction_id: Some(transaction_id.clone()), } } - pub fn get_error_reason( error_info: Option, detailed_error_info: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay.rs b/crates/hyperswitch_connectors/src/connectors/worldpay.rs index 07e4c178f9..104e854e6f 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay.rs @@ -1199,6 +1199,42 @@ impl IncomingWebhook for Worldpay { let psync_body = WorldpayEventResponse::try_from(body)?; Ok(Box::new(psync_body)) } + + fn get_mandate_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let body: WorldpayWebhookTransactionId = request + .body + .parse_struct("WorldpayWebhookTransactionId") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + let mandate_reference = body.event_details.token.map(|mandate_token| { + hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails { + connector_mandate_id: mandate_token.href, + } + }); + Ok(mandate_reference) + } + + fn get_network_txn_id( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let body: WorldpayWebhookTransactionId = request + .body + .parse_struct("WorldpayWebhookTransactionId") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + let optional_network_txn_id = body.event_details.scheme_reference.map(|network_txn_id| { + hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId::new(network_txn_id) + }); + Ok(optional_network_txn_id) + } } impl ConnectorRedirectResponse for Worldpay { diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs index 884caa9e84..52ae2c4f16 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs @@ -52,18 +52,22 @@ pub enum TokenCreationType { Worldpay, } +#[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CustomerAgreement { #[serde(rename = "type")] pub agreement_type: CustomerAgreementType, - pub stored_card_usage: StoredCardUsageType, + pub stored_card_usage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme_reference: Option>, } #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum CustomerAgreementType { Subscription, + Unscheduled, } #[derive(Clone, Debug, PartialEq, Serialize)] @@ -78,6 +82,7 @@ pub enum StoredCardUsageType { pub enum PaymentInstrument { Card(CardPayment), CardToken(CardToken), + RawCardForNTI(RawCardDetails), Googlepay(WalletPayment), Applepay(WalletPayment), } @@ -85,15 +90,22 @@ pub enum PaymentInstrument { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CardPayment { - #[serde(rename = "type")] - pub payment_type: PaymentType, + #[serde(flatten)] + pub raw_card_details: RawCardDetails, + pub cvc: Secret, #[serde(skip_serializing_if = "Option::is_none")] pub card_holder_name: Option>, - pub card_number: cards::CardNumber, - pub expiry_date: ExpiryDate, #[serde(skip_serializing_if = "Option::is_none")] pub billing_address: Option, - pub cvc: Secret, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RawCardDetails { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub card_number: cards::CardNumber, + pub expiry_date: ExpiryDate, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs index 5e9fb03042..b4d6a37040 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs @@ -43,6 +43,8 @@ pub struct AuthorizedResponse { pub fraud: Option, /// Mandate's token pub token: Option, + /// Network transaction ID + pub scheme_reference: Option>, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -429,9 +431,13 @@ pub struct WorldpayWebhookTransactionId { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EventDetails { - pub transaction_reference: String, #[serde(rename = "type")] pub event_type: EventType, + pub transaction_reference: String, + /// Mandate's token + pub token: Option, + /// Network transaction ID + pub scheme_reference: Option>, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index ec23b520a2..6ae507790d 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -26,7 +26,7 @@ use super::{requests::*, response::*}; use crate::{ types::ResponseRouterData, utils::{ - self, AddressData, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData, + self, AddressData, ApplePay, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData as RouterDataTrait, }, }; @@ -76,12 +76,14 @@ fn fetch_payment_instrument( ) -> 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()?, + raw_card_details: RawCardDetails { + 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, }, - 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) = @@ -107,6 +109,16 @@ fn fetch_payment_instrument( None }, })), + PaymentMethodData::CardDetailsForNetworkTransactionId(raw_card_details) => { + Ok(PaymentInstrument::RawCardForNTI(RawCardDetails { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: raw_card_details.get_expiry_month_as_i8()?, + year: raw_card_details.get_expiry_year_as_4_digit_i32()?, + }, + card_number: raw_card_details.card_number, + })) + } PaymentMethodData::MandatePayment => mandate_ids .and_then(|mandate_ids| { mandate_ids @@ -138,7 +150,7 @@ fn fetch_payment_instrument( })), WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment { payment_type: PaymentType::Encrypted, - wallet_token: Secret::new(data.payment_data), + wallet_token: data.get_applepay_decoded_payment_data()?, ..WalletPayment::default() })), WalletData::AliPayQr(_) @@ -185,13 +197,10 @@ fn fetch_payment_instrument( | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("worldpay"), - ) - .into()) - } + | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), } } @@ -439,6 +448,7 @@ fn get_token_and_agreement( payment_method_data: &PaymentMethodData, setup_future_usage: Option, off_session: Option, + mandate_ids: Option, ) -> (Option, Option) { match (payment_method_data, setup_future_usage, off_session) { // CIT @@ -448,7 +458,8 @@ fn get_token_and_agreement( }), Some(CustomerAgreement { agreement_type: CustomerAgreementType::Subscription, - stored_card_usage: StoredCardUsageType::First, + stored_card_usage: Some(StoredCardUsageType::First), + scheme_reference: None, }), ), // MIT @@ -456,7 +467,26 @@ fn get_token_and_agreement( None, Some(CustomerAgreement { agreement_type: CustomerAgreementType::Subscription, - stored_card_usage: StoredCardUsageType::Subsequent, + stored_card_usage: Some(StoredCardUsageType::Subsequent), + scheme_reference: None, + }), + ), + // NTI with raw card data + (PaymentMethodData::CardDetailsForNetworkTransactionId(_), _, _) => ( + None, + mandate_ids.and_then(|mandate_ids| { + mandate_ids + .mandate_reference_id + .and_then(|mandate_id| match mandate_id { + MandateReferenceId::NetworkMandateId(network_transaction_id) => { + Some(CustomerAgreement { + agreement_type: CustomerAgreementType::Unscheduled, + scheme_reference: Some(network_transaction_id.into()), + stored_card_usage: None, + }) + } + _ => None, + }) }), ), _ => (None, None), @@ -487,6 +517,7 @@ impl TryFrom<(&WorldpayRouterData<&T>, &Secret ), ) -> Result { let (router_data, optional_correlation_id) = item; - let (description, redirection_data, mandate_reference, error) = router_data + let (description, redirection_data, mandate_reference, network_txn_id, error) = router_data .response .other_fields .as_ref() @@ -660,6 +691,7 @@ impl mandate_metadata: None, connector_mandate_request_reference_id: None, }), + res.scheme_reference.clone(), None, ), WorldpayPaymentResponseFields::DDCResponse(res) => ( @@ -681,6 +713,7 @@ impl }), None, None, + None, ), WorldpayPaymentResponseFields::ThreeDsChallenged(res) => ( None, @@ -694,16 +727,18 @@ impl }), None, None, + None, ), WorldpayPaymentResponseFields::RefusedResponse(res) => ( None, None, None, + None, Some((res.refusal_code.clone(), res.refusal_description.clone())), ), - WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None), + WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None, None), }) - .unwrap_or((None, None, None, None)); + .unwrap_or((None, None, None, None, None)); let worldpay_status = router_data.response.outcome.clone(); let optional_error_message = match worldpay_status { PaymentOutcome::ThreeDsAuthenticationFailed => { @@ -725,7 +760,7 @@ impl redirection_data: Box::new(redirection_data), mandate_reference: Box::new(mandate_reference), connector_metadata: None, - network_txn_id: None, + network_txn_id: network_txn_id.map(|id| id.expose()), connector_response_reference_id: optional_correlation_id.clone(), incremental_authorization_allowed: None, charge_id: None, diff --git a/crates/hyperswitch_connectors/src/connectors/xendit.rs b/crates/hyperswitch_connectors/src/connectors/xendit.rs index 571bd87e83..dae6b8fd88 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit.rs @@ -1,13 +1,16 @@ pub mod transformers; - +use base64::Engine; +use common_enums::{enums, CallConnectorAction, CaptureMethod, PaymentAction, PaymentMethodType}; use common_utils::{ + consts::BASE64_ENGINE, errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; 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, @@ -21,39 +24,42 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ api::{ - self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, - ConnectorValidation, + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{Mask, PeekInterface}; use transformers as xendit; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentMethodDataType, RefundsRequestData}, +}; #[derive(Clone)] pub struct Xendit { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Xendit { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &FloatMajorUnitForConnector, } } } - impl api::Payment for Xendit {} impl api::PaymentSession for Xendit {} impl api::ConnectorAccessToken for Xendit {} @@ -70,7 +76,6 @@ impl api::PaymentToken for Xendit {} impl ConnectorIntegration for Xendit { - // Not Implemented (R) } impl ConnectorCommonExt for Xendit @@ -99,9 +104,6 @@ impl ConnectorCommon for 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 { @@ -118,46 +120,81 @@ impl ConnectorCommon for Xendit { ) -> CustomResult)>, errors::ConnectorError> { let auth = xendit::XenditAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Basic {encoded_api_key}").into_masked(), )]) } - fn build_error_response( &self, res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: xendit::XenditErrorResponse = res + let error_response: xendit::XenditErrorResponse = res .response - .parse_struct("XenditErrorResponse") + .parse_struct("ErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - + event_builder.map(|i| i.set_error_response_body(&error_response)); + router_env::logger::info!(connector_response=?error_response); Ok(ErrorResponse { + code: error_response.error_code.clone(), + message: error_response.message.clone(), + reason: Some(error_response.message.clone()), status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, attempt_status: None, connector_transaction_id: None, }) } } +impl ConnectorIntegration for Xendit { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotSupported { + message: "Cancel/Void flow".to_string(), + connector: "Xendit", + } + .into()) + } +} impl ConnectorValidation for Xendit { - //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 { + CaptureMethod::Automatic + | CaptureMethod::Manual + | CaptureMethod::SequentialAutomatic => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => Err( + utils::construct_not_supported_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 ConnectorIntegration for Xendit {} + impl ConnectorIntegration for Xendit { //TODO: implement sessions flow } -impl ConnectorIntegration for Xendit {} - impl ConnectorIntegration for Xendit {} impl ConnectorIntegration for Xendit { @@ -176,9 +213,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payment_requests", self.base_url(connectors))) } fn get_request_body( @@ -191,9 +228,8 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: xendit::XenditPaymentsResponse = res + let response: xendit::XenditPaymentResponse = res .response - .parse_struct("Xendit PaymentsAuthorizeResponse") + .parse_struct("XenditPaymentResponse") .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(), @@ -262,10 +295,18 @@ impl ConnectorIntegration for Xen 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 = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/payment_requests/{connector_payment_id}", + self.base_url(connectors), + )) } fn build_request( @@ -276,9 +317,9 @@ impl ConnectorIntegration for Xen Ok(Some( RequestBuilder::new() .method(Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .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(), )) } @@ -289,25 +330,20 @@ impl ConnectorIntegration for Xen event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: xendit::XenditPaymentsResponse = res + let response: xendit::XenditPaymentResponse = res .response - .parse_struct("xendit PaymentsSyncResponse") + .parse_struct("xendit XenditPaymentResponse") .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) + .change_context(errors::ConnectorError::ResponseHandlingFailed) } } @@ -326,18 +362,42 @@ 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 = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payment_requests/{connector_payment_id}/captures", + self.base_url(connectors), + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount_to_capture = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let authorized_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_payment_amount, + req.request.currency, + )?; + if amount_to_capture != authorized_amount { + Err(report!(errors::ConnectorError::NotSupported { + message: "Partial Capture".to_string(), + connector: "Xendit" + })) + } else { + let connector_router_data = xendit::XenditRouterData::from((amount_to_capture, req)); + let connector_req = + xendit::XenditPaymentsCaptureRequest::try_from(connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } } fn build_request( @@ -348,14 +408,10 @@ impl ConnectorIntegration fo Ok(Some( RequestBuilder::new() .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .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( - self, req, connectors, - )?) + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(self.get_request_body(req, connectors)?) .build(), )) } @@ -366,17 +422,20 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: xendit::XenditPaymentsResponse = res + let response: xendit::XenditPaymentResponse = res .response - .parse_struct("Xendit PaymentsCaptureResponse") + .parse_struct("Xendit 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(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( @@ -388,8 +447,6 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Xendit {} - impl ConnectorIntegration for Xendit { fn get_headers( &self, @@ -406,9 +463,9 @@ impl ConnectorIntegration for Xendit fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds", self.base_url(connectors),)) } fn get_request_body( @@ -416,13 +473,12 @@ impl ConnectorIntegration for Xendit req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - let refund_amount = utils::convert_amount( + let 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_router_data = xendit::XenditRouterData::from((amount, req)); let connector_req = xendit::XenditRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -455,14 +511,17 @@ impl ConnectorIntegration for Xendit let response: xendit::RefundResponse = res.response .parse_struct("xendit RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + 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( @@ -489,10 +548,15 @@ impl ConnectorIntegration for Xendit { 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!( + "{}/refunds/{}", + self.base_url(connectors), + connector_refund_id + )) } fn build_request( @@ -506,9 +570,6 @@ impl ConnectorIntegration for Xendit { .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(), )) } @@ -519,10 +580,11 @@ impl ConnectorIntegration for Xendit { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: xendit::RefundResponse = res - .response - .parse_struct("xendit RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + 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 { @@ -530,6 +592,7 @@ impl ConnectorIntegration for Xendit { data: data.clone(), http_code: res.status_code, }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( @@ -566,3 +629,37 @@ impl webhooks::IncomingWebhook for Xendit { } impl ConnectorSpecifications for Xendit {} + +impl ConnectorRedirectResponse for Xendit { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: PaymentAction, + ) -> CustomResult { + match action { + PaymentAction::PSync + | PaymentAction::PaymentAuthenticateCompleteAuthorize + | PaymentAction::CompleteAuthorize => Ok(CallConnectorAction::Trigger), + // PaymentAction::CompleteAuthorize => { + // let parsed_query: Vec<(String, String)> = + // form_urlencoded::parse(query_params.as_bytes()) + // .into_owned() + // .collect(); + // let status = parsed_query + // .iter() + // .find(|(key, _)| key == "status") + // .map(|(_, value)| value.as_str()); + + // match status { + // Some("VERIFIED") => Ok(CallConnectorAction::Trigger), + // _ => Ok(CallConnectorAction::StatusUpdate { + // status: enums::AttemptStatus::AuthenticationFailed, + // error_code: Some("INVALID_STATUS".to_string()), + // error_message: Some("INVALID_STATUS".to_string()), + // }), + // } + // } + } + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs index c9d4cd2583..8ff4b74c07 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -1,134 +1,426 @@ +use std::collections::HashMap; + +use cards::CardNumber; use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::{pii, request::Method, types::FloatMajorUnit}; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::{PaymentsAuthorizeData, PaymentsCaptureData, ResponseId}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, }; -use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + get_unimplemented_payment_method_error_message, CardData, PaymentsAuthorizeRequestData, + PaymentsSyncRequestData, RouterData as OtherRouterData, + }, }; //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 amount: FloatMajorUnit, // 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, - } - } +#[derive(Serialize, Deserialize, Debug)] +pub enum PaymentMethodType { + CARD, } - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] +#[derive(Serialize, Deserialize, Debug)] +pub struct ChannelProperties { + pub success_return_url: String, + pub failure_return_url: String, + pub skip_three_d_secure: bool, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum PaymentMethod { + Card(CardPaymentRequest), +} +#[derive(Serialize, Deserialize, Debug)] +pub struct CardPaymentRequest { + #[serde(rename = "type")] + pub payment_type: PaymentMethodType, + pub card: CardInfo, + pub reusability: TransactionType, + pub reference_id: Secret, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct MandatePaymentRequest { + pub amount: FloatMajorUnit, + pub currency: common_enums::Currency, + pub capture_method: String, + pub payment_method_id: Secret, + pub channel_properties: ChannelProperties, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditRedirectionResponse { + pub status: PaymentStatus, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditPaymentsCaptureRequest { + pub capture_amount: FloatMajorUnit, +} +#[derive(Serialize, Deserialize, Debug)] pub struct XenditPaymentsRequest { - amount: StringMinorUnit, - card: XenditCard, + pub amount: FloatMajorUnit, + pub currency: common_enums::Currency, + pub capture_method: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub payment_method: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub payment_method_id: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel_properties: Option, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct XenditCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Serialize, Deserialize, Debug)] +pub struct CardInfo { + pub channel_properties: ChannelProperties, + pub card_information: CardInformation, +} +#[derive(Serialize, Deserialize, Debug)] +pub struct CardInformation { + pub card_number: CardNumber, + pub expiry_month: Secret, + pub expiry_year: Secret, + pub cvv: Secret, + pub cardholder_name: Secret, + pub cardholder_email: pii::Email, + pub cardholder_phone_number: Secret, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum TransactionType { + OneTimeUse, + MultipleUse, +} +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct XenditErrorResponse { + pub error_code: String, + pub message: String, +} +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaymentStatus { + Pending, + RequiresAction, + Failed, + Succeeded, + AwaitingCapture, + Verified, } -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, - }) +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct XenditPaymentResponse { + pub id: String, + pub status: PaymentStatus, + pub actions: Option>, + pub payment_method: PaymentMethodInfo, + pub failure_code: Option, + pub reference_id: Secret, +} + +fn map_payment_response_to_attempt_status( + response: XenditPaymentResponse, + is_auto_capture: bool, +) -> enums::AttemptStatus { + match response.status { + PaymentStatus::Failed => enums::AttemptStatus::Failure, + PaymentStatus::Succeeded | PaymentStatus::Verified => { + if is_auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } + PaymentStatus::Pending => enums::AttemptStatus::Pending, + PaymentStatus::RequiresAction => enums::AttemptStatus::AuthenticationPending, + PaymentStatus::AwaitingCapture => enums::AttemptStatus::Authorized, } } -//TODO: Fill the struct with respective fields -// Auth Struct -pub struct XenditAuthType { - pub(super) api_key: Secret, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum MethodType { + Get, + Post, } - -impl TryFrom<&ConnectorAuthType> for XenditAuthType { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Action { + pub method: MethodType, + pub url: String, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PaymentMethodInfo { + pub id: Secret, +} +impl TryFrom> for XenditPaymentsRequest { type Error = error_stack::Report; - fn try_from(auth_type: &ConnectorAuthType) -> Result { - match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + fn try_from(item: XenditRouterData<&PaymentsAuthorizeRouterData>) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(card_data) => Ok(Self { + capture_method: match item.router_data.request.is_auto_capture()? { + true => "AUTOMATIC".to_string(), + false => "MANUAL".to_string(), + }, + currency: item.router_data.request.currency, + amount: item.amount, + payment_method: Some(PaymentMethod::Card(CardPaymentRequest { + payment_type: PaymentMethodType::CARD, + reference_id: Secret::new( + item.router_data.connector_request_reference_id.clone(), + ), + card: CardInfo { + channel_properties: ChannelProperties { + success_return_url: item.router_data.request.get_router_return_url()?, + failure_return_url: item.router_data.request.get_router_return_url()?, + skip_three_d_secure: !item.router_data.is_three_ds(), + }, + card_information: CardInformation { + card_number: card_data.card_number.clone(), + expiry_month: card_data.card_exp_month.clone(), + expiry_year: card_data.get_expiry_year_4_digit(), + cvv: card_data.card_cvc.clone(), + cardholder_name: card_data + .get_cardholder_name() + .or(item.router_data.get_billing_full_name())?, + cardholder_email: item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?, + cardholder_phone_number: item.router_data.get_billing_phone_number()?, + }, + }, + reusability: match item.router_data.request.is_mandate_payment() { + true => TransactionType::MultipleUse, + false => TransactionType::OneTimeUse, + }, + })), + payment_method_id: None, + channel_properties: None, }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + PaymentMethodData::MandatePayment => Ok(Self { + channel_properties: Some(ChannelProperties { + success_return_url: item.router_data.request.get_router_return_url()?, + failure_return_url: item.router_data.request.get_router_return_url()?, + skip_three_d_secure: true, + }), + capture_method: match item.router_data.request.is_auto_capture()? { + true => "AUTOMATIC".to_string(), + false => "MANUAL".to_string(), + }, + currency: item.router_data.request.currency, + amount: item.amount, + payment_method_id: Some(Secret::new( + item.router_data.request.get_connector_mandate_id()?, + )), + payment_method: None, + }), + _ => Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("xendit"), + ) + .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, - } +impl TryFrom> for XenditPaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: XenditRouterData<&PaymentsCaptureRouterData>) -> Result { + Ok(Self { + capture_amount: item.amount, + }) } } +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct XenditPaymentsResponse { - status: XenditPaymentStatus, - id: String, + fn try_from( + item: ResponseRouterData< + F, + XenditPaymentResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + let status = map_payment_response_to_attempt_status( + item.response.clone(), + item.data.request.is_auto_capture()?, + ); + let response = if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), + message: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + reason: Some( + item.response + .failure_code + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + ), + attempt_status: None, + connector_transaction_id: Some(item.response.id.clone()), + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data: match item.response.actions { + Some(actions) if !actions.is_empty() => { + actions.first().map_or(Box::new(None), |single_action| { + Box::new(Some(RedirectForm::Form { + endpoint: single_action.url.clone(), + method: match single_action.method { + MethodType::Get => Method::Get, + MethodType::Post => Method::Post, + }, + form_fields: HashMap::new(), + })) + }) + } + _ => Box::new(None), + }, + mandate_reference: match item.data.request.is_mandate_payment() { + true => Box::new(Some(MandateReference { + connector_mandate_id: Some(item.response.payment_method.id.expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + false => Box::new(None), + }, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + item.response.reference_id.peek().to_string(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }) + }; + Ok(Self { + status, + response, + ..item.data + }) + } } -impl TryFrom> - for RouterData +impl + TryFrom> + for RouterData { type Error = error_stack::Report; + fn try_from( - item: ResponseRouterData, + item: ResponseRouterData< + F, + XenditPaymentResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, ) -> Result { + let status = map_payment_response_to_attempt_status(item.response.clone(), true); + let response = if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), + message: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + reason: Some( + item.response + .failure_code + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + ), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }) + } else { + 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: Some( + item.response.reference_id.peek().to_string(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }) + }; Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + status, + response, + ..item.data + }) + } +} +impl TryFrom> for PaymentsSyncRouterData { + type Error = error_stack::Report; + fn try_from( + item: PaymentsSyncResponseRouterData, + ) -> Result { + let status = map_payment_response_to_attempt_status( + item.response.clone(), + item.data.request.is_auto_capture()?, + ); + let response = if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), + message: item + .response + .failure_code + .clone() + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + reason: Some( + item.response + .failure_code + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), + ), + attempt_status: None, + connector_transaction_id: Some(item.response.id.clone()), + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -136,18 +428,47 @@ impl TryFrom From<(FloatMajorUnit, T)> for XenditRouterData { + fn from((amount, item): (FloatMajorUnit, T)) -> Self { + Self { + amount, + router_data: item, + } + } +} +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()), + } + } +} //TODO: Fill the struct with respective fields // REFUND : // Type definition for RefundRequest #[derive(Default, Debug, Serialize)] pub struct XenditRefundRequest { - pub amount: StringMinorUnit, + pub amount: FloatMajorUnit, + pub payment_request_id: String, + pub reason: String, } impl TryFrom<&XenditRouterData<&RefundsRouterData>> for XenditRefundRequest { @@ -155,34 +476,34 @@ impl TryFrom<&XenditRouterData<&RefundsRouterData>> for XenditRefundReques fn try_from(item: &XenditRouterData<&RefundsRouterData>) -> Result { Ok(Self { amount: item.amount.to_owned(), + payment_request_id: item.router_data.request.connector_transaction_id.clone(), + reason: "REQUESTED_BY_CUSTOMER".to_string(), }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RefundStatus { + RequiresAction, Succeeded, Failed, - #[default] - Processing, + Pending, + Cancelled, } 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 + RefundStatus::Failed | RefundStatus::Cancelled => Self::Failure, + RefundStatus::Pending | RefundStatus::RequiresAction => Self::Pending, } } } //TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundResponse { id: String, status: RefundStatus, @@ -195,7 +516,7 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), + connector_refund_id: item.response.id, refund_status: enums::RefundStatus::from(item.response.status), }), ..item.data @@ -217,12 +538,3 @@ impl TryFrom> for RefundsRouter }) } } - -//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/hyperswitch_connectors/src/connectors/zsl.rs b/crates/hyperswitch_connectors/src/connectors/zsl.rs index 434cfbc343..cf01c31b74 100644 --- a/crates/hyperswitch_connectors/src/connectors/zsl.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl.rs @@ -458,6 +458,7 @@ lazy_static! { mandates: common_enums::FeatureStatus::NotSupported, refunds: common_enums::FeatureStatus::NotSupported, supported_capture_methods, + specific_features: None, }, ); diff --git a/crates/hyperswitch_connectors/src/constants.rs b/crates/hyperswitch_connectors/src/constants.rs index 3d99f8d167..e83438d325 100644 --- a/crates/hyperswitch_connectors/src/constants.rs +++ b/crates/hyperswitch_connectors/src/constants.rs @@ -32,3 +32,11 @@ pub(crate) mod headers { /// Unsupported response type error message pub const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type"; + +/// Error message for Authentication Error from the connector +pub const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector"; + +/// Error message when Refund request has been voided. +pub const REFUND_VOIDED: &str = "Refund request has been voided."; + +pub const LOW_BALANCE_ERROR_MESSAGE: &str = "Insufficient balance in the payment method"; diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 703b0bb2ac..a2db7ad7b6 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -99,6 +99,7 @@ default_imp_for_authorize_session_token!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -106,6 +107,7 @@ default_imp_for_authorize_session_token!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -144,6 +146,7 @@ default_imp_for_authorize_session_token!( connectors::Tsys, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl, @@ -169,6 +172,7 @@ default_imp_for_calculate_tax!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -176,6 +180,7 @@ default_imp_for_calculate_tax!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -214,6 +219,7 @@ default_imp_for_calculate_tax!( connectors::Volt, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl, @@ -239,6 +245,7 @@ default_imp_for_session_update!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -246,6 +253,7 @@ default_imp_for_session_update!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Digitalvirgo, connectors::Dlocal, @@ -279,6 +287,7 @@ default_imp_for_session_update!( connectors::Gocardless, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl, @@ -310,6 +319,7 @@ default_imp_for_post_session_tokens!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Bitpay, connectors::Bluesnap, connectors::Boku, @@ -317,6 +327,7 @@ default_imp_for_post_session_tokens!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Digitalvirgo, connectors::Dlocal, @@ -349,6 +360,7 @@ default_imp_for_post_session_tokens!( connectors::Gocardless, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Powertranz, connectors::Prophetpay, @@ -381,6 +393,7 @@ macro_rules! default_imp_for_complete_authorize { default_imp_for_complete_authorize!( connectors::Amazonpay, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Boku, @@ -415,6 +428,7 @@ default_imp_for_complete_authorize!( connectors::Thunes, connectors::Tsys, connectors::UnifiedAuthenticationService, + connectors::Wellsfargo, connectors::Worldline, connectors::Volt, connectors::Xendit, @@ -443,6 +457,7 @@ default_imp_for_incremental_authorization!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -515,6 +530,7 @@ default_imp_for_create_customer!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -522,6 +538,7 @@ default_imp_for_create_customer!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -558,6 +575,7 @@ default_imp_for_create_customer!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -588,9 +606,11 @@ default_imp_for_connector_redirect_response!( connectors::Bitpay, connectors::Boku, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Dlocal, @@ -623,9 +643,9 @@ default_imp_for_connector_redirect_response!( connectors::Thunes, connectors::Tsys, connectors::UnifiedAuthenticationService, + connectors::Wellsfargo, connectors::Worldline, connectors::Volt, - connectors::Xendit, connectors::Zsl, connectors::CtpMastercard ); @@ -649,6 +669,7 @@ default_imp_for_pre_processing_steps!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -691,6 +712,7 @@ default_imp_for_pre_processing_steps!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -718,6 +740,7 @@ default_imp_for_post_processing_steps!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -725,6 +748,7 @@ default_imp_for_post_processing_steps!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -763,6 +787,7 @@ default_imp_for_post_processing_steps!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -790,6 +815,7 @@ default_imp_for_approve!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -797,6 +823,7 @@ default_imp_for_approve!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -835,6 +862,7 @@ default_imp_for_approve!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -862,6 +890,7 @@ default_imp_for_reject!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -869,6 +898,7 @@ default_imp_for_reject!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -907,6 +937,7 @@ default_imp_for_reject!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -934,6 +965,7 @@ default_imp_for_webhook_source_verification!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -941,6 +973,7 @@ default_imp_for_webhook_source_verification!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -979,6 +1012,7 @@ default_imp_for_webhook_source_verification!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1007,6 +1041,7 @@ default_imp_for_accept_dispute!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1014,6 +1049,7 @@ default_imp_for_accept_dispute!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1052,6 +1088,7 @@ default_imp_for_accept_dispute!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1079,6 +1116,7 @@ default_imp_for_submit_evidence!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1086,6 +1124,7 @@ default_imp_for_submit_evidence!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1124,6 +1163,7 @@ default_imp_for_submit_evidence!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1151,6 +1191,7 @@ default_imp_for_defend_dispute!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1158,6 +1199,7 @@ default_imp_for_defend_dispute!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1196,6 +1238,7 @@ default_imp_for_defend_dispute!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1232,6 +1275,7 @@ default_imp_for_file_upload!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1239,6 +1283,7 @@ default_imp_for_file_upload!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1277,6 +1322,7 @@ default_imp_for_file_upload!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1297,6 +1343,7 @@ default_imp_for_payouts!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1342,6 +1389,7 @@ default_imp_for_payouts!( connectors::Volt, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl, @@ -1370,6 +1418,7 @@ default_imp_for_payouts_create!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1377,6 +1426,7 @@ default_imp_for_payouts_create!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1415,6 +1465,7 @@ default_imp_for_payouts_create!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1444,6 +1495,7 @@ default_imp_for_payouts_retrieve!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1451,6 +1503,7 @@ default_imp_for_payouts_retrieve!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1489,6 +1542,7 @@ default_imp_for_payouts_retrieve!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1518,6 +1572,7 @@ default_imp_for_payouts_eligibility!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1525,6 +1580,7 @@ default_imp_for_payouts_eligibility!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1563,6 +1619,7 @@ default_imp_for_payouts_eligibility!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1592,6 +1649,7 @@ default_imp_for_payouts_fulfill!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1637,6 +1695,7 @@ default_imp_for_payouts_fulfill!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1666,6 +1725,7 @@ default_imp_for_payouts_cancel!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1673,6 +1733,7 @@ default_imp_for_payouts_cancel!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1711,6 +1772,7 @@ default_imp_for_payouts_cancel!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1740,6 +1802,7 @@ default_imp_for_payouts_quote!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1747,6 +1810,7 @@ default_imp_for_payouts_quote!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1785,6 +1849,7 @@ default_imp_for_payouts_quote!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1814,6 +1879,7 @@ default_imp_for_payouts_recipient!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1821,6 +1887,7 @@ default_imp_for_payouts_recipient!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1859,6 +1926,7 @@ default_imp_for_payouts_recipient!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1888,6 +1956,7 @@ default_imp_for_payouts_recipient_account!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1895,6 +1964,7 @@ default_imp_for_payouts_recipient_account!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1933,6 +2003,7 @@ default_imp_for_payouts_recipient_account!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -1962,6 +2033,7 @@ default_imp_for_frm_sale!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1969,6 +2041,7 @@ default_imp_for_frm_sale!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2007,6 +2080,7 @@ default_imp_for_frm_sale!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2036,6 +2110,7 @@ default_imp_for_frm_checkout!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2043,6 +2118,7 @@ default_imp_for_frm_checkout!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2081,6 +2157,7 @@ default_imp_for_frm_checkout!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2110,6 +2187,7 @@ default_imp_for_frm_transaction!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2117,6 +2195,7 @@ default_imp_for_frm_transaction!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2155,6 +2234,7 @@ default_imp_for_frm_transaction!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2184,6 +2264,7 @@ default_imp_for_frm_fulfillment!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2191,6 +2272,7 @@ default_imp_for_frm_fulfillment!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2229,6 +2311,7 @@ default_imp_for_frm_fulfillment!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2258,6 +2341,7 @@ default_imp_for_frm_record_return!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2265,6 +2349,7 @@ default_imp_for_frm_record_return!( connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2303,6 +2388,7 @@ default_imp_for_frm_record_return!( connectors::UnifiedAuthenticationService, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2329,6 +2415,7 @@ default_imp_for_revoking_mandates!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2401,6 +2488,7 @@ default_imp_for_uas_pre_authentication!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bluesnap, connectors::Bitpay, @@ -2409,6 +2497,7 @@ default_imp_for_uas_pre_authentication!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2446,6 +2535,7 @@ default_imp_for_uas_pre_authentication!( connectors::Tsys, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, @@ -2471,6 +2561,7 @@ default_imp_for_uas_post_authentication!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -2479,6 +2570,7 @@ default_imp_for_uas_post_authentication!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -2516,6 +2608,7 @@ default_imp_for_uas_post_authentication!( connectors::Tsys, connectors::Worldline, connectors::Worldpay, + connectors::Wellsfargo, connectors::Volt, connectors::Xendit, connectors::Zen, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 4161b36f90..485ff86d72 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -209,6 +209,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -217,6 +218,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -256,6 +258,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -282,6 +285,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -290,6 +294,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -326,6 +331,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Thunes, connectors::Tsys, connectors::UnifiedAuthenticationService, + connectors::Wellsfargo, connectors::Worldline, connectors::Volt, connectors::Worldpay, @@ -350,6 +356,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -358,6 +365,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -397,6 +405,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -424,6 +433,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -471,6 +481,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -497,6 +508,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -544,6 +556,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -570,6 +583,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -578,6 +592,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -617,6 +632,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -653,6 +669,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -661,6 +678,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -700,6 +718,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -728,6 +747,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -736,6 +756,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -775,6 +796,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -803,6 +825,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -811,6 +834,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -850,6 +874,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -878,6 +903,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -886,6 +912,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -925,6 +952,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -953,6 +981,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -961,6 +990,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1000,6 +1030,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1028,6 +1059,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1036,6 +1068,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1075,6 +1108,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1103,6 +1137,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1111,6 +1146,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1150,6 +1186,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1178,6 +1215,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1186,6 +1224,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1225,6 +1264,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1253,6 +1293,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1261,6 +1302,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1300,6 +1342,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1326,6 +1369,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1334,6 +1378,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1373,6 +1418,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1401,6 +1447,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1409,6 +1456,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1448,6 +1496,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1476,6 +1525,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1484,6 +1534,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1523,6 +1574,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1551,6 +1603,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1559,6 +1612,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1598,6 +1652,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1626,6 +1681,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1634,6 +1690,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1673,6 +1730,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1701,6 +1759,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1709,6 +1768,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1748,6 +1808,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl @@ -1773,6 +1834,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, + connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, @@ -1781,6 +1843,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, + connectors::Cybersource, connectors::Datatrans, connectors::Deutschebank, connectors::Digitalvirgo, @@ -1820,6 +1883,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Worldline, connectors::Volt, connectors::Worldpay, + connectors::Wellsfargo, connectors::Xendit, connectors::Zen, connectors::Zsl diff --git a/crates/hyperswitch_connectors/src/types.rs b/crates/hyperswitch_connectors/src/types.rs index 05ef62d782..f731450f38 100644 --- a/crates/hyperswitch_connectors/src/types.rs +++ b/crates/hyperswitch_connectors/src/types.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "payouts")] +use hyperswitch_domain_models::types::{PayoutsData, PayoutsResponseData}; use hyperswitch_domain_models::{ router_data::{AccessToken, RouterData}, router_flow_types::{AccessTokenAuth, Capture, PSync, PreProcessing, Session, Void}, @@ -25,6 +27,10 @@ pub(crate) type PaymentsPreprocessingResponseRouterData = pub(crate) type PaymentsSessionResponseRouterData = ResponseRouterData; +#[cfg(feature = "payouts")] +pub type PayoutsResponseRouterData = + ResponseRouterData; + // TODO: Remove `ResponseRouterData` from router crate after all the related type aliases are moved to this crate. pub struct ResponseRouterData { pub response: R, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 9061a758be..e99777ad27 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1,6 +1,8 @@ use std::collections::{HashMap, HashSet}; use api_models::payments; +#[cfg(feature = "payouts")] +use api_models::payouts::PayoutVendorAccountDetails; use base64::Engine; use common_enums::{ enums, @@ -17,15 +19,15 @@ use common_utils::{ use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ address::{Address, AddressDetails, PhoneDetails}, - payment_method_data::{self, Card, PaymentMethodData}, + payment_method_data::{self, Card, CardDetailsForNetworkTransactionId, PaymentMethodData}, router_data::{ ApplePayPredecryptData, ErrorResponse, PaymentMethodToken, RecurringMandatePaymentData, }, router_request_types::{ AuthenticationData, BrowserInformation, CompleteAuthorizeData, ConnectorCustomerData, - PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, - PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSyncData, RefundsData, ResponseId, - SetupMandateRequestData, + MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSyncData, + RefundsData, ResponseId, SetupMandateRequestData, }, types::OrderDetailsWithAmount, }; @@ -946,6 +948,7 @@ pub trait CardData { 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>; + fn get_cardholder_name(&self) -> Result, Error>; } impl CardData for Card { @@ -1032,6 +1035,102 @@ impl CardData for Card { .change_context(errors::ConnectorError::ResponseDeserializationFailed) .map(Secret::new) } + fn get_cardholder_name(&self) -> Result, Error> { + self.card_holder_name + .clone() + .ok_or_else(missing_field_err("card.card_holder_name")) + } +} + +impl CardData for 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_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() + .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) + } + 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) + } + fn get_cardholder_name(&self) -> Result, Error> { + self.card_holder_name + .clone() + .ok_or_else(missing_field_err("card.card_holder_name")) + } } #[track_caller] @@ -1278,6 +1377,8 @@ pub trait PaymentsAuthorizeRequestData { &self, ) -> Result, Error>; fn is_cit_mandate_payment(&self) -> bool; + fn get_optional_network_transaction_id(&self) -> Option; + fn get_optional_email(&self) -> Option; } impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { @@ -1338,9 +1439,7 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { } fn is_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 - })) + && (self.setup_future_usage == Some(FutureUsage::OffSession))) || self .mandate_id .as_ref() @@ -1409,9 +1508,7 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { fn is_customer_initiated_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 - }) + && self.setup_future_usage == Some(FutureUsage::OffSession) } fn get_metadata_as_object(&self) -> Option { @@ -1469,10 +1566,23 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { } 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 + && self.setup_future_usage == Some(FutureUsage::OffSession) + } + fn get_optional_network_transaction_id(&self) -> Option { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::NetworkMandateId(network_transaction_id)) => { + Some(network_transaction_id.clone()) + } + Some(payments::MandateReferenceId::ConnectorMandateId(_)) + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) + | None => None, }) } + fn get_optional_email(&self) -> Option { + self.email.clone() + } } pub trait PaymentsCaptureRequestData { @@ -1595,6 +1705,9 @@ pub trait PaymentsSetupMandateRequestData { fn get_email(&self) -> Result; fn get_router_return_url(&self) -> Result; fn is_card(&self) -> bool; + fn get_return_url(&self) -> Result; + fn get_webhook_url(&self) -> Result; + fn get_optional_language_from_browser_info(&self) -> Option; } impl PaymentsSetupMandateRequestData for SetupMandateRequestData { @@ -1614,6 +1727,21 @@ impl PaymentsSetupMandateRequestData for SetupMandateRequestData { fn is_card(&self) -> bool { matches!(self.payment_method_data, PaymentMethodData::Card(_)) } + fn get_return_url(&self) -> Result { + self.router_return_url + .clone() + .ok_or_else(missing_field_err("return_url")) + } + fn get_webhook_url(&self) -> Result { + self.webhook_url + .clone() + .ok_or_else(missing_field_err("webhook_url")) + } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } } pub trait PaymentMethodTokenizationRequestData { @@ -1669,9 +1797,7 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { } fn is_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 - })) + && self.setup_future_usage == Some(FutureUsage::OffSession)) || self .mandate_id .as_ref() @@ -1694,9 +1820,7 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { } 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 - }) + && self.setup_future_usage == Some(FutureUsage::OffSession) } } pub trait AddressData { @@ -1711,12 +1835,41 @@ impl AddressData for Address { } } pub trait PaymentsPreProcessingRequestData { - fn get_amount(&self) -> Result; + fn get_redirect_response_payload(&self) -> Result; + fn get_email(&self) -> Result; + fn get_payment_method_type(&self) -> Result; fn get_currency(&self) -> Result; + fn get_amount(&self) -> Result; + fn get_minor_amount(&self) -> Result; fn is_auto_capture(&self) -> Result; + fn get_order_details(&self) -> Result, Error>; + fn get_webhook_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; } impl PaymentsPreProcessingRequestData for PaymentsPreProcessingData { + fn get_email(&self) -> Result { + self.email.clone().ok_or_else(missing_field_err("email")) + } + fn get_payment_method_type(&self) -> Result { + self.payment_method_type + .to_owned() + .ok_or_else(missing_field_err("payment_method_type")) + } + fn get_currency(&self) -> Result { + self.currency.ok_or_else(missing_field_err("currency")) + } + fn get_amount(&self) -> Result { + self.amount.ok_or_else(missing_field_err("amount")) + } + + // New minor amount function for amount framework + fn get_minor_amount(&self) -> Result { + self.minor_amount.ok_or_else(missing_field_err("amount")) + } fn is_auto_capture(&self) -> Result { match self.capture_method { Some(enums::CaptureMethod::Automatic) @@ -1728,11 +1881,53 @@ impl PaymentsPreProcessingRequestData for PaymentsPreProcessingData { } } } - fn get_amount(&self) -> Result { - self.amount.ok_or_else(missing_field_err("amount")) + fn get_order_details(&self) -> Result, Error> { + self.order_details + .clone() + .ok_or_else(missing_field_err("order_details")) } - fn get_currency(&self) -> Result { - self.currency.ok_or_else(missing_field_err("currency")) + fn get_webhook_url(&self) -> Result { + self.webhook_url + .clone() + .ok_or_else(missing_field_err("webhook_url")) + } + fn get_router_return_url(&self) -> Result { + self.router_return_url + .clone() + .ok_or_else(missing_field_err("return_url")) + } + fn get_browser_info(&self) -> Result { + self.browser_info + .clone() + .ok_or_else(missing_field_err("browser_info")) + } + fn get_complete_authorize_url(&self) -> Result { + self.complete_authorize_url + .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.get_connector_mandate_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) } } @@ -2439,3 +2634,180 @@ pub fn deserialize_xml_to_struct( Ok(result) } + +#[cfg(feature = "payouts")] +pub trait PayoutsData { + fn get_transfer_id(&self) -> Result; + fn get_customer_details( + &self, + ) -> Result; + fn get_vendor_details(&self) -> Result; + #[cfg(feature = "payouts")] + fn get_payout_type(&self) -> Result; +} + +#[cfg(feature = "payouts")] +impl PayoutsData for hyperswitch_domain_models::router_request_types::PayoutsData { + fn get_transfer_id(&self) -> Result { + self.connector_payout_id + .clone() + .ok_or_else(missing_field_err("transfer_id")) + } + fn get_customer_details( + &self, + ) -> Result { + self.customer_details + .clone() + .ok_or_else(missing_field_err("customer_details")) + } + fn get_vendor_details(&self) -> Result { + self.vendor_details + .clone() + .ok_or_else(missing_field_err("vendor_details")) + } + #[cfg(feature = "payouts")] + fn get_payout_type(&self) -> Result { + self.payout_type + .to_owned() + .ok_or_else(missing_field_err("payout_type")) + } +} +pub trait RevokeMandateRequestData { + fn get_connector_mandate_id(&self) -> Result; +} + +impl RevokeMandateRequestData for MandateRevokeRequestData { + fn get_connector_mandate_id(&self) -> Result { + self.connector_mandate_id + .clone() + .ok_or_else(missing_field_err("connector_mandate_id")) + } +} +pub trait RecurringMandateData { + fn get_original_payment_amount(&self) -> Result; + fn get_original_payment_currency(&self) -> Result; +} + +impl RecurringMandateData for RecurringMandatePaymentData { + fn get_original_payment_amount(&self) -> Result { + self.original_payment_authorized_amount + .ok_or_else(missing_field_err("original_payment_authorized_amount")) + } + fn get_original_payment_currency(&self) -> Result { + self.original_payment_authorized_currency + .ok_or_else(missing_field_err("original_payment_authorized_currency")) + } +} + +#[cfg(feature = "payouts")] +impl CardData for api_models::payouts::CardPayout { + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { + let binding = self.expiry_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.expiry_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.expiry_month.peek() + )) + } + fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret { + let year = self.get_expiry_year_4_digit(); + Secret::new(format!( + "{}{}{}", + self.expiry_month.peek(), + delimiter, + year.peek() + )) + } + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.expiry_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.expiry_month.clone().expose(); + Ok(Secret::new(format!("{year}{month}"))) + } + fn get_expiry_month_as_i8(&self) -> Result, Error> { + self.expiry_month + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } + fn get_expiry_year_as_i32(&self) -> Result, Error> { + self.expiry_year + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } + + fn get_expiry_date_as_mmyy(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); + let month = self.expiry_month.clone().expose(); + Ok(Secret::new(format!("{month}{year}"))) + } + + 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) + } + fn get_cardholder_name(&self) -> Result, Error> { + self.card_holder_name + .clone() + .ok_or_else(missing_field_err("card.card_holder_name")) + } +} + +pub trait NetworkTokenData { + fn get_card_issuer(&self) -> Result; + fn get_expiry_year_4_digit(&self) -> Secret; +} + +impl NetworkTokenData for payment_method_data::NetworkTokenData { + fn get_card_issuer(&self) -> Result { + get_card_issuer(self.token_number.peek()) + } + + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.token_exp_year.peek().clone(); + if year.len() == 2 { + year = format!("20{}", year); + } + Secret::new(year) + } +} diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 1df474f18d..47c505e8ca 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -59,7 +59,8 @@ pub struct Profile { pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -101,7 +102,8 @@ pub struct ProfileSetter { pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -201,7 +203,8 @@ pub struct ProfileGeneralUpdate { pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, pub is_click_to_pay_enabled: Option, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v1")] @@ -729,7 +732,8 @@ pub struct Profile { pub version: common_enums::ApiVersion, pub is_network_tokenization_enabled: bool, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] @@ -771,7 +775,8 @@ pub struct ProfileSetter { pub is_tax_connector_enabled: bool, pub is_network_tokenization_enabled: bool, pub is_click_to_pay_enabled: bool, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] @@ -872,7 +877,8 @@ pub struct ProfileGeneralUpdate { pub order_fulfillment_time_origin: Option, pub is_network_tokenization_enabled: Option, pub is_click_to_pay_enabled: Option, - pub authentication_product_ids: Option, + pub authentication_product_ids: + Option, } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/callback_mapper.rs b/crates/hyperswitch_domain_models/src/callback_mapper.rs new file mode 100644 index 0000000000..fffc33e572 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/callback_mapper.rs @@ -0,0 +1,12 @@ +use common_utils::pii; +use serde::{self, Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct CallbackMapper { + pub id: String, + #[serde(rename = "type")] + pub type_: String, + pub data: pii::SecretSerdeValue, + pub created_at: time::PrimitiveDateTime, + pub last_modified_at: time::PrimitiveDateTime, +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 7a170f918e..bd6dd2e537 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -2,6 +2,7 @@ pub mod address; pub mod api; pub mod behaviour; pub mod business_profile; +pub mod callback_mapper; pub mod consts; pub mod customer; pub mod disputes; diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index 7789c05e99..ed52325c23 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "v2")] -use api_models::admin; use common_utils::{ crypto::Encryptable, date_time, @@ -12,14 +10,10 @@ use common_utils::{ 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; -#[cfg(feature = "v2")] -use crate::errors::api_error_response::ApiErrorResponse; use crate::{ router_data, type_encryption::{crypto_operation, CryptoOperation}, @@ -62,7 +56,6 @@ 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 @@ -72,6 +65,11 @@ impl MerchantConnectorAccount { .clone() .parse_value("ConnectorAuthType") } + + pub fn get_connector_wallets_details(&self) -> Option> { + self.connector_wallets_details.as_deref().cloned() + } + pub fn get_connector_test_mode(&self) -> Option { self.test_mode } @@ -82,7 +80,7 @@ impl MerchantConnectorAccount { pub struct MerchantConnectorAccount { pub id: id_type::MerchantConnectorAccountId, pub merchant_id: id_type::MerchantId, - pub connector_name: String, + pub connector_name: common_enums::connector_enums::Connector, #[encrypt] pub connector_account_details: Encryptable>, pub disabled: Option, @@ -130,6 +128,11 @@ impl MerchantConnectorAccount { .clone() .parse_value("ConnectorAuthType") } + + pub fn get_connector_wallets_details(&self) -> Option> { + self.connector_wallets_details.as_deref().cloned() + } + pub fn get_connector_test_mode(&self) -> Option { todo!() } @@ -142,7 +145,7 @@ impl MerchantConnectorAccount { pub struct PaymentMethodsEnabledForConnector { pub payment_methods_enabled: common_types::payment_methods::RequestPaymentMethodTypes, pub payment_method: common_enums::PaymentMethod, - pub connector: String, + pub connector: common_enums::connector_enums::Connector, } #[cfg(feature = "v2")] @@ -172,11 +175,8 @@ impl FlattenedPaymentMethodsEnabled { 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), + std::iter::repeat((connector_name, payment_method.payment_method_type)) + .take(length), ) }) }) @@ -184,7 +184,7 @@ impl FlattenedPaymentMethodsEnabled { |(request_payment_methods, (connector_name, payment_method))| { PaymentMethodsEnabledForConnector { payment_methods_enabled: request_payment_methods, - connector: connector_name.clone(), + connector: connector_name, payment_method, } }, diff --git a/crates/hyperswitch_domain_models/src/relay.rs b/crates/hyperswitch_domain_models/src/relay.rs index 959ac8e7f6..8af58265c3 100644 --- a/crates/hyperswitch_domain_models/src/relay.rs +++ b/crates/hyperswitch_domain_models/src/relay.rs @@ -81,7 +81,7 @@ impl RelayUpdate { match response { Err(error) => Self::ErrorUpdate { error_code: error.code, - error_message: error.message, + error_message: error.reason.unwrap_or(error.message), status: common_enums::RelayStatus::Failure, }, Ok(response) => Self::StatusUpdate { diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 2970ac127e..8e4c79a965 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -22,6 +22,7 @@ pub struct RouterData { // Make this change after all the connector dependency has been removed from connectors pub payment_id: String, pub attempt_id: String, + pub tenant_id: id_type::TenantId, pub status: common_enums::enums::AttemptStatus, pub payment_method: common_enums::enums::PaymentMethod, pub connector_auth_type: ConnectorAuthType, diff --git a/crates/hyperswitch_domain_models/src/router_data_v2.rs b/crates/hyperswitch_domain_models/src/router_data_v2.rs index acc7343cb1..7ca106331e 100644 --- a/crates/hyperswitch_domain_models/src/router_data_v2.rs +++ b/crates/hyperswitch_domain_models/src/router_data_v2.rs @@ -2,6 +2,7 @@ pub mod flow_common_types; use std::{marker::PhantomData, ops::Deref}; +use common_utils::id_type; #[cfg(feature = "frm")] pub use flow_common_types::FrmFlowData; #[cfg(feature = "payouts")] @@ -16,6 +17,7 @@ use crate::router_data::{ConnectorAuthType, ErrorResponse}; #[derive(Debug, Clone)] pub struct RouterDataV2 { pub flow: PhantomData, + pub tenant_id: id_type::TenantId, pub resource_common_data: ResourceCommonData, pub connector_auth_type: ConnectorAuthType, /// Contains flow-specific data required to construct a request and send it to the connector. diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index aa9fb2b5f1..2f8594662e 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -491,6 +491,27 @@ pub struct BrowserInformation { pub device_model: Option, } +#[cfg(feature = "v2")] +impl From for BrowserInformation { + fn from(value: common_utils::types::BrowserInformation) -> Self { + Self { + color_depth: value.color_depth, + java_enabled: value.java_enabled, + java_script_enabled: value.java_script_enabled, + language: value.language, + screen_height: value.screen_height, + screen_width: value.screen_width, + time_zone: value.time_zone, + ip_address: value.ip_address, + accept_header: value.accept_header, + user_agent: value.user_agent, + os_type: value.os_type, + os_version: value.os_version, + device_model: value.device_model, + } + } +} + #[derive(Debug, Clone, Default, Serialize)] pub enum ResponseId { ConnectorTransactionId(String), @@ -871,6 +892,7 @@ pub struct SetupMandateRequestData { pub off_session: Option, pub setup_mandate_details: Option, pub router_return_url: Option, + pub webhook_url: Option, pub browser_info: Option, pub email: Option, pub customer_name: Option>, diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 61453f36b8..1a1488031f 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -236,6 +236,10 @@ pub enum RedirectForm { access_token: String, step_up_url: String, }, + DeutschebankThreeDSChallengeFlow { + acs_url: String, + creq: String, + }, Payme, Braintree { client_token: String, @@ -313,6 +317,9 @@ impl From for diesel_models::payment_attempt::RedirectForm { access_token, step_up_url, }, + RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { + Self::DeutschebankThreeDSChallengeFlow { acs_url, creq } + } RedirectForm::Payme => Self::Payme, RedirectForm::Braintree { client_token, @@ -392,6 +399,9 @@ impl From for RedirectForm { access_token, step_up_url, }, + diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { + Self::DeutschebankThreeDSChallengeFlow { acs_url, creq } + } diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme, diesel_models::payment_attempt::RedirectForm::Braintree { client_token, @@ -521,6 +531,8 @@ pub struct PaymentMethodDetails { pub refunds: common_enums::FeatureStatus, /// List of supported capture methods pub supported_capture_methods: Vec, + /// Payment method specific features + pub specific_features: Option, } /// list of payment method types and metadata related to them diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index 2dd38d88b6..4bbab24edf 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -3,10 +3,10 @@ pub use diesel_models::types::OrderDetailsWithAmount; use crate::{ router_data::{AccessToken, RouterData}, router_flow_types::{ - AccessTokenAuth, Authorize, AuthorizeSessionToken, CalculateTax, Capture, - CompleteAuthorize, CreateConnectorCustomer, Execute, PSync, PaymentMethodToken, - PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, - SetupMandate, Void, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authorize, AuthorizeSessionToken, + CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, Execute, + IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, PostSessionTokens, + PreAuthenticate, PreProcessing, RSync, Session, SetupMandate, Void, }, router_request_types::{ unified_authentication_service::{ @@ -14,15 +14,19 @@ use crate::{ UasPreAuthenticationRequestData, }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, - ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsPostSessionTokensData, + ConnectorCustomerData, MandateRevokeRequestData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, SetupMandateRequestData, }, router_response_types::{ - PaymentsResponseData, RefundsResponseData, TaxCalculationResponseData, + MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData, + TaxCalculationResponseData, }, }; +#[cfg(feature = "payouts")] +pub use crate::{router_request_types::PayoutsData, router_response_types::PayoutsResponseData}; pub type PaymentsAuthorizeRouterData = RouterData; @@ -56,3 +60,14 @@ pub type UasPostAuthenticationRouterData = pub type UasPreAuthenticationRouterData = RouterData; + +pub type MandateRevokeRouterData = + RouterData; +pub type PaymentsIncrementalAuthorizationRouterData = RouterData< + IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, +>; + +#[cfg(feature = "payouts")] +pub type PayoutsRouterData = RouterData; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 2f068b5609..3a634ae2a5 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -85,8 +85,8 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_post_session_tokens, // Routes for relay - routes::relay, - routes::relay_retrieve, + routes::relay::relay, + routes::relay::relay_retrieve, // Routes for refunds routes::refunds::refunds_create, @@ -624,6 +624,7 @@ 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::SuccessRateSpecificityLevel, api_models::routing::ToggleDynamicRoutingQuery, api_models::routing::ToggleDynamicRoutingPath, api_models::routing::ast::RoutableChoiceKind, @@ -694,6 +695,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::feature_matrix::FeatureMatrixListResponse, api_models::feature_matrix::FeatureMatrixRequest, api_models::feature_matrix::ConnectorFeatureMatrixResponse, + api_models::feature_matrix::PaymentMethodSpecificFeatures, + api_models::feature_matrix::CardSpecificFeatures, api_models::feature_matrix::SupportedPaymentMethod, )), modifiers(&SecurityAddon) diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 12b0fd0035..3b1fefefa1 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -128,15 +128,14 @@ Never share your secret api keys. Keep them guarded and secure. 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::list_payment_methods, 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::payment_method::list_customer_payment_method_api, //Routes for refunds routes::refunds::refunds_create, @@ -208,7 +207,6 @@ Never share your secret api keys. Keep them guarded and secure. 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, @@ -477,14 +475,15 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AmountDetailsResponse, api_models::payments::BankCodeResponse, api_models::payments::PaymentMethodListResponseForPayments, + api_models::payments::ResponsePaymentMethodTypesForPayments, api_models::payment_methods::RequiredFieldInfo, - api_models::payment_methods::DefaultPaymentMethod, api_models::payment_methods::MaskedBankDetails, api_models::payment_methods::SurchargeDetailsResponse, api_models::payment_methods::SurchargeResponse, api_models::payment_methods::SurchargePercentage, api_models::payment_methods::PaymentMethodCollectLinkRequest, api_models::payment_methods::PaymentMethodCollectLinkResponse, + api_models::payment_methods::PaymentMethodSubtypeSpecificData, api_models::payments::PaymentsRetrieveResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, @@ -644,6 +643,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::feature_matrix::FeatureMatrixListResponse, api_models::feature_matrix::FeatureMatrixRequest, api_models::feature_matrix::ConnectorFeatureMatrixResponse, + api_models::feature_matrix::PaymentMethodSpecificFeatures, + api_models::feature_matrix::CardSpecificFeatures, api_models::feature_matrix::SupportedPaymentMethod, common_utils::types::BrowserInformation, api_models::payments::PaymentAmountDetailsResponse, diff --git a/crates/openapi/src/routes.rs b/crates/openapi/src/routes.rs index 2c1c582441..4486df82b0 100644 --- a/crates/openapi/src/routes.rs +++ b/crates/openapi/src/routes.rs @@ -19,8 +19,3 @@ 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::*, relay::*, routing::*, webhook_events::*, -}; diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs index b38a234267..afc9f4a173 100644 --- a/crates/openapi/src/routes/payment_method.rs +++ b/crates/openapi/src/routes/payment_method.rs @@ -322,46 +322,27 @@ pub async fn payment_method_update_api() {} #[cfg(feature = "v2")] pub async fn payment_method_delete_api() {} -/// List customer saved payment methods for a payment +/// Payment Methods - Payment Methods List /// -/// 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" = [])) -)] +/// List the payment methods eligible for a payment method. #[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 + path = "/v2/payment-methods/{id}/list-enabled-payment-methods", + params( + ("id" = String, Path, description = "The global payment method id"), + ( + "X-Profile-Id" = String, Header, + description = "Profile ID associated to the payment method intent", + example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + ), ), responses( - (status = 200, description = "Payment Methods retrieved", body = CustomerPaymentMethodsListResponse), - (status = 400, description = "Invalid Data"), - (status = 404, description = "Payment Methods does not exist in records") + (status = 200, description = "Get the payment methods", body = PaymentMethodListResponseForPayments), + (status = 404, description = "No payment method found with the given id") ), tag = "Payment Methods", - operation_id = "List all Payment Methods for a Customer", - security(("api_key" = [])) + operation_id = "List Payment methods for a Payment Method Intent", + security(("api_key" = [], "ephemeral_key" = [])) )] -#[cfg(feature = "v2")] -pub async fn list_customer_payment_method_api() {} +pub fn list_payment_methods() {} diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 3c7ffa16ad..56e9ab9810 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -849,20 +849,23 @@ impl super::RedisConnectionPool { } #[instrument(level = "DEBUG", skip(self))] - pub async fn incr_keys_using_script( + pub async fn evaluate_redis_script( &self, lua_script: &'static str, key: Vec, values: V, - ) -> CustomResult<(), errors::RedisError> + ) -> CustomResult where V: TryInto + Debug + Send + Sync, V::Error: Into + Send + Sync, + T: serde::de::DeserializeOwned + FromRedis, { - self.pool + let val: T = self + .pool .eval(lua_script, key, values) .await - .change_context(errors::RedisError::IncrementHashFieldFailed) + .change_context(errors::RedisError::IncrementHashFieldFailed)?; + Ok(val) } } @@ -955,11 +958,10 @@ mod tests { .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]) + redis.call("INCRBY", KEYS[i], ARGV[i]) end - return results + return "#; let mut keys_and_values = HashMap::new(); for i in 0..10 { @@ -973,7 +975,45 @@ mod tests { .collect::>(); // Act - let result = pool.incr_keys_using_script(lua_script, key, values).await; + let result = pool + .evaluate_redis_script::<_, ()>(lua_script, key, values) + .await; + + // Assert Setup + result.is_ok() + }) + }) + .await + .expect("Spawn block failure"); + + assert!(is_success); + } + #[tokio::test] + async fn test_getting_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("GET", KEYS[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::>(); + + // Act + let result = pool + .evaluate_redis_script::<_, String>(lua_script, key, 0) + .await; // Assert Setup result.is_ok() diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 619d2fb193..db65482628 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 = ["keymanager_create","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 = [] diff --git a/crates/router/locales/en.yml b/crates/router/locales/en.yml index c85be7ccf4..67eec844fb 100644 --- a/crates/router/locales/en.yml +++ b/crates/router/locales/en.yml @@ -7,7 +7,7 @@ payout_link: status: title: "Payout Status" info: - ref_id: "Ref Id" + ref_id: "Reference Id" error_code: "Error Code" error_message: "Error Message" message: diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index 8fe30aa59d..8fb4f2ac2c 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -1925,32 +1925,57 @@ 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_and_org_id(&state, &role_id, &auth.org_id) - .await - .change_context(UserErrors::InternalServerError) - .change_context(OpenSearchError::UnknownError)?; + let role_info = RoleInfo::from_role_id_org_id_tenant_id( + &state, + &role_id, + &auth.org_id, + auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_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 user_roles: HashSet = match role_info.get_entity_type() { + EntityType::Tenant => 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: None, + 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(), + EntityType::Organization | EntityType::Merchant | EntityType::Profile => 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 @@ -1959,12 +1984,15 @@ pub mod routes { let state = Arc::clone(&state); let role_id = user_role.role_id.clone(); let org_id = user_role.org_id.clone().unwrap_or_default(); + let tenant_id = &user_role.tenant_id; 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)) + RoleInfo::from_role_id_org_id_tenant_id( + &state, &role_id, &org_id, tenant_id, + ) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError) + .map(|role_info| (role_id, role_info)) } }) .collect::>() @@ -2047,32 +2075,57 @@ pub mod routes { indexed_req, |state, auth: UserFromToken, req, _| async move { let role_id = auth.role_id; - 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 role_info = RoleInfo::from_role_id_org_id_tenant_id( + &state, + &role_id, + &auth.org_id, + auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_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 user_roles: HashSet = match role_info.get_entity_type() { + EntityType::Tenant => 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: None, + 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(), + EntityType::Organization | EntityType::Merchant | EntityType::Profile => 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() @@ -2080,12 +2133,15 @@ pub mod routes { let state = Arc::clone(&state); let role_id = user_role.role_id.clone(); let org_id = user_role.org_id.clone().unwrap_or_default(); + let tenant_id = &user_role.tenant_id; 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)) + RoleInfo::from_role_id_org_id_tenant_id( + &state, &role_id, &org_id, tenant_id, + ) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError) + .map(|role_info| (role_id, role_info)) } }) .collect::>() diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index 62c8c03e1a..63dc9a5863 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -35,15 +35,8 @@ async fn main() -> CustomResult<(), ProcessTrackerError> { let conf = Settings::with_config_path(cmd_line.config_path) .expect("Unable to construct application configuration"); let api_client = Box::new( - services::ProxyClient::new( - conf.proxy.clone(), - services::proxy_bypass_urls( - conf.key_manager.get_inner(), - &conf.locker, - &conf.proxy.bypass_proxy_urls, - ), - ) - .change_context(ProcessTrackerError::ConfigurationError)?, + services::ProxyClient::new(&conf.proxy) + .change_context(ProcessTrackerError::ConfigurationError)?, ); // channel for listening to redis disconnect events let (redis_shutdown_signal_tx, redis_shutdown_signal_rx) = oneshot::channel(); @@ -158,7 +151,7 @@ pub async fn deep_health_check( let app_state = Arc::clone(&state.into_inner()); let service_name = service.into_inner(); for (tenant, _) in stores { - let session_state_res = app_state.clone().get_session_state(&tenant, || { + let session_state_res = app_state.clone().get_session_state(&tenant, None, || { errors::ApiErrorResponse::MissingRequiredField { field_name: "tenant_id", } @@ -397,7 +390,7 @@ async fn start_scheduler( WorkflowRunner {}, |state, tenant| { Arc::new(state.clone()) - .get_session_state(tenant, || ProcessTrackerError::TenantNotFound.into()) + .get_session_state(tenant, None, || ProcessTrackerError::TenantNotFound.into()) }, ) .await diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 74f6502159..8912d551f9 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use api_models::{enums, payment_methods::RequiredFieldInfo}; +use common_utils::id_type; #[cfg(feature = "payouts")] pub mod payout_required_fields; @@ -58,7 +59,7 @@ impl Default for super::settings::Proxy { http_url: Default::default(), https_url: Default::default(), idle_pool_connection_timeout: Some(90), - bypass_proxy_urls: Vec::new(), + bypass_proxy_hosts: Default::default(), } } } @@ -138,6 +139,17 @@ impl Default for super::settings::KvConfig { } } +impl Default for super::settings::GlobalTenant { + fn default() -> Self { + Self { + tenant_id: id_type::TenantId::get_default_global_tenant_id(), + schema: String::from("global"), + redis_key_prefix: String::from("global"), + clickhouse_database: String::from("global"), + } + } +} + #[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 index 9e42aec4a5..d957109bce 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -67,6 +67,7 @@ impl Default for Mandates { enums::Connector::Authorizedotnet, enums::Connector::Globalpay, enums::Connector::Worldpay, + enums::Connector::Fiuu, enums::Connector::Multisafepay, enums::Connector::Nexinets, enums::Connector::Noon, @@ -88,6 +89,7 @@ impl Default for Mandates { enums::Connector::Authorizedotnet, enums::Connector::Globalpay, enums::Connector::Worldpay, + enums::Connector::Fiuu, enums::Connector::Multisafepay, enums::Connector::Nexinets, enums::Connector::Noon, @@ -105,6 +107,7 @@ impl Default for Mandates { } } +#[cfg(feature = "v1")] impl Default for settings::RequiredFields { fn default() -> Self { Self(HashMap::from([ @@ -941,6 +944,129 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Deutschebank, + 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.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, + } + ), + ( + "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, + } + ) + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Dlocal, RequiredFieldFinal { @@ -1019,7 +1145,7 @@ impl Default for settings::RequiredFields { common:HashMap::new(), } ), - #[cfg(feature = "dummy_connector")] + #[cfg(feature = "dummy_connector")] ( enums::Connector::DummyConnector1, RequiredFieldFinal { @@ -3224,7 +3350,8 @@ impl Default for settings::RequiredFields { enums::Connector::Worldpay, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: { + non_mandate: HashMap::new(), + common: { let mut pmd_fields = HashMap::from([ ( "payment_method_data.card.card_number".to_string(), @@ -3257,7 +3384,72 @@ impl Default for settings::RequiredFields { pmd_fields.extend(get_worldpay_billing_required_fields()); pmd_fields }, - common: HashMap::new(), + } + ), + ( + enums::Connector::Xendit, + 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.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.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, + } + ) + + ] + ), } ), ( @@ -4138,6 +4330,129 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Deutschebank, + 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.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, + } + ), + ( + "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, + } + ) + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Dlocal, RequiredFieldFinal { @@ -6420,7 +6735,8 @@ impl Default for settings::RequiredFields { enums::Connector::Worldpay, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: { + non_mandate: HashMap::new(), + common: { let mut pmd_fields = HashMap::from([ ( "payment_method_data.card.card_number".to_string(), @@ -6453,7 +6769,72 @@ impl Default for settings::RequiredFields { pmd_fields.extend(get_worldpay_billing_required_fields()); pmd_fields }, - common: HashMap::new(), + } + ), + ( + enums::Connector::Xendit, + 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.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.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, + } + ) + + ] + ), } ), ( @@ -9750,7 +10131,7 @@ impl Default for settings::RequiredFields { "VI".to_string(), "WF".to_string(), "EH".to_string(), - "ZM".to_string(), + "ZM".to_string(), ] }, value: None, @@ -12528,7 +12909,7 @@ impl Default for settings::RequiredFields { "ES".to_string(), "FR".to_string(), "IE".to_string(), - "NL".to_string(), + "NL".to_string(), ], }, value: None, @@ -12840,18 +13221,18 @@ impl Default for settings::RequiredFields { pub fn get_worldpay_billing_required_fields() -> HashMap { HashMap::from([ ( - "billing.address.zip".to_string(), + "billing.address.line1".to_string(), RequiredFieldInfo { - required_field: "billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, value: None, }, ), ( "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "billing.address.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserAddressCountry { options: vec![ @@ -12994,5 +13375,14 @@ pub fn get_worldpay_billing_required_fields() -> HashMap Self { Self(HashMap::from([ @@ -65,6 +66,7 @@ impl Default for PayoutRequiredFields { } } +#[cfg(feature = "v1")] fn get_connector_payment_method_type_fields( connector: PayoutConnectors, payment_method_type: PaymentMethodType, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index ad5d9e89aa..577b29b053 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -39,6 +39,8 @@ use crate::{ events::EventsConfig, }; +pub const REQUIRED_FIELDS_CONFIG_FILE: &str = "payment_required_fields_v2.toml"; + #[derive(clap::Parser, Default)] #[cfg_attr(feature = "vergen", command(version = router_env::version!()))] pub struct CmdLineConf { @@ -137,7 +139,7 @@ pub struct Platform { pub enabled: bool, } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Clone, Default, Deserialize)] pub struct Multitenancy { pub tenants: TenantConfig, pub enabled: bool, @@ -195,8 +197,10 @@ impl storage_impl::config::TenantConfig for Tenant { } } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone)] pub struct GlobalTenant { + #[serde(default = "id_type::TenantId::get_default_global_tenant_id")] + pub tenant_id: id_type::TenantId, pub schema: String, pub redis_key_prefix: String, pub clickhouse_database: String, @@ -535,9 +539,11 @@ pub struct NotAvailableFlows { #[cfg(feature = "payouts")] #[derive(Debug, Deserialize, Clone)] +#[cfg_attr(feature = "v2", derive(Default))] // Configs are read from the config file in config/payout_required_fields.toml pub struct PayoutRequiredFields(pub HashMap); #[derive(Debug, Deserialize, Clone)] +#[cfg_attr(feature = "v2", derive(Default))] // Configs are read from the config file in config/payment_required_fields.toml pub struct RequiredFields(pub HashMap); #[derive(Debug, Deserialize, Clone)] @@ -548,6 +554,7 @@ pub struct ConnectorFields { pub fields: HashMap, } +#[cfg(feature = "v1")] #[derive(Debug, Deserialize, Clone)] pub struct RequiredFieldFinal { pub mandate: HashMap, @@ -555,6 +562,14 @@ pub struct RequiredFieldFinal { pub common: HashMap, } +#[cfg(feature = "v2")] +#[derive(Debug, Deserialize, Clone)] +pub struct RequiredFieldFinal { + pub mandate: Option>, + pub non_mandate: Option>, + pub common: Option>, +} + #[derive(Debug, Default, Deserialize, Clone)] #[serde(default)] pub struct Secrets { @@ -623,7 +638,7 @@ pub struct Proxy { pub http_url: Option, pub https_url: Option, pub idle_pool_connection_timeout: Option, - pub bypass_proxy_urls: Vec, + pub bypass_proxy_hosts: Option, } #[derive(Debug, Deserialize, Clone)] @@ -795,7 +810,16 @@ impl Settings { let config = router_env::Config::builder(&environment.to_string()) .change_context(ApplicationError::ConfigurationError)? - .add_source(File::from(config_path).required(false)) + .add_source(File::from(config_path).required(false)); + + #[cfg(feature = "v2")] + let config = { + let required_fields_config_file = + router_env::Config::get_config_directory().join(REQUIRED_FIELDS_CONFIG_FILE); + config.add_source(File::from(required_fields_config_file).required(false)) + }; + + let config = config .add_source( Environment::with_prefix("ROUTER") .try_parsing(true) @@ -804,7 +828,6 @@ impl Settings { .with_list_parse_key("log.telemetry.route_to_trace") .with_list_parse_key("redis.cluster_urls") .with_list_parse_key("events.kafka.brokers") - .with_list_parse_key("proxy.bypass_proxy_urls") .with_list_parse_key("connectors.supported.wallets") .with_list_parse_key("connector_request_reference_id_config.merchant_ids_send_payment_id_as_connector_request_id"), diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 0da0e711fb..0bd9190f11 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -2,10 +2,8 @@ pub mod aci; pub mod adyen; pub mod adyenplatform; pub mod authorizedotnet; -pub mod bankofamerica; pub mod braintree; pub mod checkout; -pub mod cybersource; #[cfg(feature = "dummy_connector")] pub mod dummyconnector; pub mod ebanx; @@ -31,16 +29,16 @@ pub mod stripe; pub mod threedsecureio; pub mod trustpay; pub mod utils; -pub mod wellsfargo; pub mod wellsfargopayout; pub mod wise; pub use hyperswitch_connectors::connectors::{ 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, + bamboraapac, bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, 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, cybersource, + cybersource::Cybersource, 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, @@ -52,20 +50,19 @@ pub use hyperswitch_connectors::connectors::{ 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, + unified_authentication_service::UnifiedAuthenticationService, volt, volt::Volt, wellsfargo, + wellsfargo::Wellsfargo, 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, 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, + braintree::Braintree, checkout::Checkout, 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, wellsfargopayout::Wellsfargopayout, wise::Wise, }; diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index b9ab7ea87e..d6f6fdc8f0 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -1001,7 +1001,7 @@ impl reason: Some(consts::LOW_BALANCE_ERROR_MESSAGE.to_string()), status_code: res.status_code, attempt_status: Some(enums::AttemptStatus::Failure), - connector_transaction_id: None, + connector_transaction_id: Some(response.psp_reference), }), ..data.clone() }) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 9698b4ac5d..d2b212e3ea 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -702,39 +702,41 @@ impl &domain::Card, ), ) -> Result { - let (profile, customer) = if item - .router_data - .request - .setup_future_usage - .map_or(false, |future_usage| { - matches!(future_usage, common_enums::FutureUsage::OffSession) - }) - && (item.router_data.request.customer_acceptance.is_some() - || item - .router_data - .request - .setup_mandate_details - .clone() - .map_or(false, |mandate_details| { - mandate_details.customer_acceptance.is_some() - })) { - ( - Some(ProfileDetails::CreateProfileDetails(CreateProfileDetails { - create_profile: true, - })), - Some(CustomerDetails { - //The payment ID is included in the customer details because the connector requires unique customer information with a length of fewer than 20 characters when creating a mandate. - //If the length exceeds 20 characters, a random alphanumeric string is used instead. - id: if item.router_data.payment_id.len() <= 20 { - item.router_data.payment_id.clone() - } else { - Alphanumeric.sample_string(&mut rand::thread_rng(), 20) - }, - }), - ) - } else { - (None, None) - }; + let (profile, customer) = + if item + .router_data + .request + .setup_future_usage + .is_some_and(|future_usage| { + matches!(future_usage, common_enums::FutureUsage::OffSession) + }) + && (item.router_data.request.customer_acceptance.is_some() + || item + .router_data + .request + .setup_mandate_details + .clone() + .is_some_and(|mandate_details| { + mandate_details.customer_acceptance.is_some() + })) + { + ( + Some(ProfileDetails::CreateProfileDetails(CreateProfileDetails { + create_profile: true, + })), + Some(CustomerDetails { + //The payment ID is included in the customer details because the connector requires unique customer information with a length of fewer than 20 characters when creating a mandate. + //If the length exceeds 20 characters, a random alphanumeric string is used instead. + id: if item.router_data.payment_id.len() <= 20 { + item.router_data.payment_id.clone() + } else { + Alphanumeric.sample_string(&mut rand::thread_rng(), 20) + }, + }), + ) + } else { + (None, None) + }; Ok(Self { transaction_type: TransactionType::try_from(item.router_data.request.capture_method)?, amount: item.amount, diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index 15a2ff6903..226067cef9 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::{consts as common_consts, request::RequestContent}; use diesel_models::enums; use error_stack::{report, ResultExt}; @@ -62,12 +62,18 @@ where req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self) - .to_string() - .into(), - )]; + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self) + .to_string() + .into(), + ), + ( + common_consts::TENANT_HEADER.to_string(), + req.tenant_id.get_string_repr().to_string().into(), + ), + ]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); Ok(header) diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index ac6aad8c13..a69798314b 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1388,7 +1388,8 @@ impl From for Browser { browser_user_agent: value.user_agent, challenge_window_size: Some(ChallengeWindowSizeEnum::FullScreen), browser_javascript_enabled: value.java_script_enabled, - accept_language: None, + // Hardcoding to "en" for now, as there's no accept_language in BrowserInformation + accept_language: Some(vec!["en".to_string()]), } } } diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 3e75130f57..b96283247b 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -879,7 +879,7 @@ impl TryFrom> transaction .transaction_reference .clone() - .map_or(false, |transaction_instance| { + .is_some_and(|transaction_instance| { transaction_instance == item.data.request.refund_id }) }) diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 85b1dd221f..cbf1867b08 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -893,9 +893,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn is_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 - })) + && self.setup_future_usage == Some(storage_enums::FutureUsage::OffSession)) || self .mandate_id .as_ref() @@ -904,9 +902,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { } 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 - }) + && self.setup_future_usage == Some(storage_enums::FutureUsage::OffSession) } fn get_webhook_url(&self) -> Result { self.webhook_url @@ -973,9 +969,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn is_customer_initiated_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 - }) + && self.setup_future_usage == Some(storage_enums::FutureUsage::OffSession) } fn get_metadata_as_object(&self) -> Option { @@ -1123,9 +1117,7 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { } fn is_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 - })) + && self.setup_future_usage == Some(storage_enums::FutureUsage::OffSession)) || self .mandate_id .as_ref() @@ -1134,9 +1126,7 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { } 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 - }) + && self.setup_future_usage == Some(storage_enums::FutureUsage::OffSession) } /// Attempts to retrieve the connector mandate reference ID as a `Result`. fn get_connector_mandate_request_reference_id(&self) -> Result { diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index e2784b968a..9e00bcd6bc 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -41,7 +41,6 @@ pub const DEFAULT_LIST_API_LIMIT: u16 = 10; pub(crate) const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type"; pub(crate) const LOW_BALANCE_ERROR_MESSAGE: &str = "Insufficient balance in the payment method"; pub(crate) const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector"; -pub(crate) const REFUND_VOIDED: &str = "Refund request has been voided."; pub(crate) const CANNOT_CONTINUE_AUTH: &str = "Cannot continue with Authorization due to failed Liability Shift."; @@ -210,3 +209,10 @@ pub const DYNAMIC_ROUTING_MAX_VOLUME: u8 = 100; /// Click To Pay pub const CLICK_TO_PAY: &str = "click_to_pay"; + +/// Merchant eligible for authentication service config +pub const AUTHENTICATION_SERVICE_ELIGIBLE_CONFIG: &str = + "merchants_eligible_for_authentication_service"; + +/// Refund flow identifier used for performing GSM operations +pub const REFUND_FLOW_STR: &str = "refund_flow"; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 19c3e6c1e2..356b4a31cc 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -7,7 +7,7 @@ use api_models::{ use common_utils::{ date_time, ext_traits::{AsyncExt, Encode, OptionExt, ValueExt}, - id_type, pii, type_name, + fp_utils, id_type, pii, type_name, types::keymanager::{self as km_types, KeyManagerState, ToEncryptable}, }; use diesel_models::configs; @@ -1550,6 +1550,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { worldpay::transformers::WorldpayAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Xendit => { + xendit::transformers::XenditAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Zen => { zen::transformers::ZenAuthType::try_from(self.auth_type)?; Ok(()) @@ -1748,7 +1752,7 @@ struct PMAuthConfigValidation<'a> { key_manager_state: &'a KeyManagerState, } -impl<'a> PMAuthConfigValidation<'a> { +impl PMAuthConfigValidation<'_> { async fn validate_pm_auth(&self, val: &pii::SecretSerdeValue) -> RouterResponse<()> { let config = serde_json::from_value::( val.clone().expose(), @@ -1865,7 +1869,7 @@ struct MerchantDefaultConfigUpdate<'a> { transaction_type: &'a api_enums::TransactionType, } #[cfg(feature = "v1")] -impl<'a> MerchantDefaultConfigUpdate<'a> { +impl MerchantDefaultConfigUpdate<'_> { async fn retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists( &self, ) -> RouterResult<()> { @@ -1938,11 +1942,7 @@ impl<'a> MerchantDefaultConfigUpdate<'a> { }; 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 - }) + mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id) }); routing::helpers::update_merchant_default_config( self.store, @@ -1954,11 +1954,7 @@ impl<'a> MerchantDefaultConfigUpdate<'a> { } 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 - }) + mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id) }); routing::helpers::update_merchant_default_config( self.store, @@ -1982,7 +1978,7 @@ struct DefaultFallbackRoutingConfigUpdate<'a> { key_manager_state: &'a KeyManagerState, } #[cfg(feature = "v2")] -impl<'a> DefaultFallbackRoutingConfigUpdate<'a> { +impl DefaultFallbackRoutingConfigUpdate<'_> { async fn retrieve_and_update_default_fallback_routing_algorithm_if_routable_connector_exists( &self, ) -> RouterResult<()> { @@ -2025,11 +2021,7 @@ impl<'a> DefaultFallbackRoutingConfigUpdate<'a> { }; 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 - }) + (mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id)) }); profile_wrapper @@ -2111,19 +2103,12 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect let metadata = self.metadata.clone().or(mca.metadata.clone()); - let connector_name = mca.connector_name.as_ref(); - let connector_enum = api_models::enums::Connector::from_str(connector_name) - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "connector", - }) - .attach_printable_lazy(|| { - format!("unable to parse connector name {connector_name:?}") - })?; let connector_auth_type_and_metadata_validation = ConnectorAuthTypeAndMetadataValidation { - connector_name: &connector_enum, + connector_name: &mca.connector_name, auth_type: &auth, connector_meta_data: &metadata, }; + connector_auth_type_and_metadata_validation.validate_auth_and_metadata_type()?; let connector_status_and_disabled_validation = ConnectorStatusAndDisabledValidation { status: &self.status, @@ -2131,6 +2116,7 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect auth: &auth, current_status: &mca.status, }; + let (connector_status, disabled) = connector_status_and_disabled_validation.validate_status_and_disabled()?; @@ -2153,7 +2139,7 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect merchant_account.get_id(), &auth, &self.connector_type, - &connector_enum, + &mca.connector_name, types::AdditionalMerchantData::foreign_from(data.clone()), ) .await?, @@ -2520,7 +2506,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), connector_type: self.connector_type, - connector_name: self.connector_name.to_string(), + connector_name: self.connector_name, connector_account_details: encrypted_data.connector_account_details, payment_methods_enabled, disabled, @@ -2819,11 +2805,16 @@ pub async fn create_connector( let store = state.store.as_ref(); let key_manager_state = &(&state).into(); #[cfg(feature = "dummy_connector")] - req.connector_name - .validate_dummy_connector_enabled(state.conf.dummy_connector.enabled) - .change_context(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid connector name".to_string(), - })?; + fp_utils::when( + req.connector_name + .validate_dummy_connector_create(state.conf.dummy_connector.enabled), + || { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector name".to_string(), + }) + }, + )?; + let connector_metadata = ConnectorMetadata { connector_metadata: &req.metadata, }; @@ -3331,11 +3322,11 @@ pub async fn delete_connector( let merchant_default_config_delete = DefaultFallbackRoutingConfigUpdate { routable_connector: &Some( - common_enums::RoutableConnectors::from_str(&mca.connector_name).map_err(|_| { - errors::ApiErrorResponse::InvalidDataValue { + common_enums::RoutableConnectors::from_str(&mca.connector_name.to_string()).map_err( + |_| errors::ApiErrorResponse::InvalidDataValue { field_name: "connector_name", - } - })?, + }, + )?, ), merchant_connector_id: &mca.get_id(), store: db, @@ -3627,13 +3618,6 @@ 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(), @@ -3704,7 +3688,7 @@ impl ProfileCreateBridge for api::ProfileCreate { 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, + authentication_product_ids: self.authentication_product_ids, })) } @@ -3756,13 +3740,6 @@ 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(), @@ -3820,7 +3797,7 @@ impl ProfileCreateBridge for api::ProfileCreate { 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, + authentication_product_ids: self.authentication_product_ids, })) } } @@ -4028,13 +4005,6 @@ 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, @@ -4078,7 +4048,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate { 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, + authentication_product_ids: self.authentication_product_ids, }, ))) } @@ -4141,13 +4111,6 @@ 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, @@ -4183,7 +4146,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate { .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, + authentication_product_ids: self.authentication_product_ids, }, ))) } diff --git a/crates/router/src/core/apple_pay_certificates_migration.rs b/crates/router/src/core/apple_pay_certificates_migration.rs index 44be8f33bb..21f38e5956 100644 --- a/crates/router/src/core/apple_pay_certificates_migration.rs +++ b/crates/router/src/core/apple_pay_certificates_migration.rs @@ -13,6 +13,7 @@ use crate::{ types::{domain::types as domain_types, storage}, }; +#[cfg(feature = "v1")] pub async fn apple_pay_certificates_migration( state: SessionState, req: &apple_pay_certificates_migration::ApplePayCertificatesMigrationRequest, diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index 09cebda490..bedd7787a7 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -42,6 +42,7 @@ pub async fn perform_authentication( psd2_sca_exemption_type: Option, ) -> CustomResult { let router_data = transformers::construct_authentication_router_data( + state, merchant_id, authentication_connector.clone(), payment_method_data, @@ -108,6 +109,7 @@ pub async fn perform_post_authentication( .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?; if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled { let router_data = transformers::construct_post_authentication_router_data( + state, authentication_connector.to_string(), business_profile, three_ds_connector_account, @@ -151,6 +153,7 @@ pub async fn perform_pre_authentication( let authentication = if authentication_connector.is_separate_version_call_required() { let router_data: core_types::authentication::PreAuthNVersionCallRouterData = transformers::construct_pre_authentication_router_data( + state, authentication_connector_name.clone(), card_number.clone(), &three_ds_connector_account, @@ -178,6 +181,7 @@ pub async fn perform_pre_authentication( let router_data: core_types::authentication::PreAuthNRouterData = transformers::construct_pre_authentication_router_data( + state, authentication_connector_name.clone(), card_number, &three_ds_connector_account, diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 30373d1408..4e7e005c94 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -15,6 +15,7 @@ use crate::{ transformers::{ForeignFrom, ForeignTryFrom}, }, utils::ext_traits::OptionExt, + SessionState, }; const IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW: &str = @@ -24,6 +25,7 @@ const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW: &str = #[allow(clippy::too_many_arguments)] pub fn construct_authentication_router_data( + state: &SessionState, merchant_id: common_utils::id_type::MerchantId, authentication_connector: String, payment_method_data: domain::PaymentMethodData, @@ -65,6 +67,7 @@ pub fn construct_authentication_router_data( webhook_url, }; construct_router_data( + state, authentication_connector, payment_method, merchant_id.clone(), @@ -76,6 +79,7 @@ pub fn construct_authentication_router_data( } pub fn construct_post_authentication_router_data( + state: &SessionState, authentication_connector: String, business_profile: domain::Profile, merchant_connector_account: payments_helpers::MerchantConnectorAccountType, @@ -90,6 +94,7 @@ pub fn construct_post_authentication_router_data( threeds_server_transaction_id, }; construct_router_data( + state, authentication_connector, PaymentMethod::default(), business_profile.merchant_id.clone(), @@ -101,6 +106,7 @@ pub fn construct_post_authentication_router_data( } pub fn construct_pre_authentication_router_data( + state: &SessionState, authentication_connector: String, card_holder_account_number: cards::CardNumber, merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, @@ -116,6 +122,7 @@ pub fn construct_pre_authentication_router_data( card_holder_account_number, }; construct_router_data( + state, authentication_connector, PaymentMethod::default(), merchant_id, @@ -126,7 +133,9 @@ pub fn construct_pre_authentication_router_data( ) } +#[allow(clippy::too_many_arguments)] pub fn construct_router_data( + state: &SessionState, authentication_connector_name: String, payment_method: PaymentMethod, merchant_id: common_utils::id_type::MerchantId, @@ -144,6 +153,7 @@ pub fn construct_router_data( flow: PhantomData, merchant_id, customer_id: None, + tenant_id: state.tenant.tenant_id.clone(), connector_customer: None, connector: authentication_connector_name, payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("authentication") diff --git a/crates/router/src/core/blocklist/transformers.rs b/crates/router/src/core/blocklist/transformers.rs index b5b3d6625e..f1194d7d68 100644 --- a/crates/router/src/core/blocklist/transformers.rs +++ b/crates/router/src/core/blocklist/transformers.rs @@ -32,7 +32,7 @@ impl ForeignFrom for blocklist::AddToBlocklistResponse { } } -async fn generate_fingerprint_request<'a>( +async fn generate_fingerprint_request( jwekey: &settings::Jwekey, locker: &settings::Locker, payload: &blocklist::GenerateFingerprintRequest, diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 65bbf736f1..4f69cf4196 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -314,7 +314,7 @@ struct AddressStructForDbEntry<'a> { } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl<'a> AddressStructForDbEntry<'a> { +impl AddressStructForDbEntry<'_> { async fn encrypt_customer_address_and_set_to_db( &self, db: &dyn StorageInterface, @@ -1029,7 +1029,7 @@ struct AddressStructForDbUpdate<'a> { } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl<'a> AddressStructForDbUpdate<'a> { +impl AddressStructForDbUpdate<'_> { async fn update_address_if_sent( &self, db: &dyn StorageInterface, @@ -1131,7 +1131,7 @@ struct VerifyIdForUpdateCustomer<'a> { } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl<'a> VerifyIdForUpdateCustomer<'a> { +impl VerifyIdForUpdateCustomer<'_> { async fn verify_id_and_get_customer_object( &self, db: &dyn StorageInterface, @@ -1152,7 +1152,7 @@ impl<'a> VerifyIdForUpdateCustomer<'a> { } #[cfg(all(feature = "v2", feature = "customer_v2"))] -impl<'a> VerifyIdForUpdateCustomer<'a> { +impl VerifyIdForUpdateCustomer<'_> { async fn verify_id_and_get_customer_object( &self, db: &dyn StorageInterface, diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 15bdbafb13..c450139fc5 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -79,6 +79,7 @@ pub async fn accept_dispute( todo!() } +#[cfg(feature = "v1")] #[instrument(skip(state))] pub async fn get_filters_for_disputes( state: SessionState, diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 99a6081730..a2a9864aad 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -379,7 +379,7 @@ where #[cfg(feature = "v2")] #[allow(clippy::too_many_arguments)] -pub async fn make_frm_data_and_fraud_check_operation<'a, F, D>( +pub async fn make_frm_data_and_fraud_check_operation( _db: &dyn StorageInterface, state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -402,7 +402,7 @@ where #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] -pub async fn make_frm_data_and_fraud_check_operation<'a, F, D>( +pub async fn make_frm_data_and_fraud_check_operation( _db: &dyn StorageInterface, state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -486,7 +486,7 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn pre_payment_frm_core<'a, F, Req, D>( +pub async fn pre_payment_frm_core( state: &SessionState, merchant_account: &domain::MerchantAccount, payment_data: &mut D, @@ -578,7 +578,7 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn post_payment_frm_core<'a, F, D>( +pub async fn post_payment_frm_core( state: &SessionState, req_state: ReqState, merchant_account: &domain::MerchantAccount, @@ -674,7 +674,7 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn call_frm_before_connector_call<'a, F, Req, D>( +pub async fn call_frm_before_connector_call( operation: &BoxedOperation<'_, F, Req, D>, merchant_account: &domain::MerchantAccount, payment_data: &mut D, 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 c413208b20..6711b96b06 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -47,7 +47,7 @@ impl ConstructFlowSpecificData( &self, - _state: &SessionState, + state: &SessionState, connector_id: &str, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, @@ -75,6 +75,7 @@ impl ConstructFlowSpecificData for FrmCheckoutRouter } } -pub async fn decide_frm_flow<'a, 'b>( - router_data: &'b mut FrmCheckoutRouterData, - state: &'a SessionState, +pub async fn decide_frm_flow( + router_data: &mut FrmCheckoutRouterData, + state: &SessionState, connector: &FraudCheckConnectorData, call_connector_action: payments::CallConnectorAction, _merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs b/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs index 640e808d54..4dbe17d17b 100644 --- a/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs +++ b/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs @@ -71,6 +71,7 @@ pub async fn construct_fulfillment_router_data<'a>( let router_data = RouterData { flow: std::marker::PhantomData, merchant_id: merchant_account.get_id().clone(), + tenant_id: state.tenant.tenant_id.clone(), connector, payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), attempt_id: payment_attempt.attempt_id.clone(), 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 5e35403118..d639345e22 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -45,7 +45,7 @@ impl ConstructFlowSpecificData( &self, - _state: &SessionState, + state: &SessionState, connector_id: &str, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, @@ -69,6 +69,7 @@ impl ConstructFlowSpecificData for FrmRecordReturnRou } } -pub async fn decide_frm_flow<'a, 'b>( - router_data: &'b mut FrmRecordReturnRouterData, - state: &'a SessionState, +pub async fn decide_frm_flow( + router_data: &mut FrmRecordReturnRouterData, + state: &SessionState, connector: &FraudCheckConnectorData, call_connector_action: payments::CallConnectorAction, _merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/fraud_check/flows/sale_flow.rs b/crates/router/src/core/fraud_check/flows/sale_flow.rs index dde9ba52a2..34fbda2c68 100644 --- a/crates/router/src/core/fraud_check/flows/sale_flow.rs +++ b/crates/router/src/core/fraud_check/flows/sale_flow.rs @@ -42,7 +42,7 @@ impl ConstructFlowSpecificData( &self, - _state: &SessionState, + state: &SessionState, connector_id: &str, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, @@ -69,6 +69,7 @@ impl ConstructFlowSpecificData for FrmSaleRouterData { } } -pub async fn decide_frm_flow<'a, 'b>( - router_data: &'b mut FrmSaleRouterData, - state: &'a SessionState, +pub async fn decide_frm_flow( + router_data: &mut FrmSaleRouterData, + state: &SessionState, connector: &FraudCheckConnectorData, call_connector_action: payments::CallConnectorAction, _merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/fraud_check/flows/transaction_flow.rs b/crates/router/src/core/fraud_check/flows/transaction_flow.rs index 673753ce4e..1675fdfb38 100644 --- a/crates/router/src/core/fraud_check/flows/transaction_flow.rs +++ b/crates/router/src/core/fraud_check/flows/transaction_flow.rs @@ -49,7 +49,7 @@ impl #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] async fn construct_router_data<'a>( &self, - _state: &SessionState, + state: &SessionState, connector_id: &str, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, @@ -77,6 +77,7 @@ impl let router_data = RouterData { flow: std::marker::PhantomData, merchant_id: merchant_account.get_id().clone(), + tenant_id: state.tenant.tenant_id.clone(), customer_id, connector: connector_id.to_string(), payment_id: self.payment_intent.payment_id.get_string_repr().to_owned(), @@ -184,9 +185,9 @@ impl FeatureFrm for FrmTransact } } -pub async fn decide_frm_flow<'a, 'b>( - router_data: &'b mut FrmTransactionRouterData, - state: &'a SessionState, +pub async fn decide_frm_flow( + router_data: &mut FrmTransactionRouterData, + state: &SessionState, connector: &frm_api::FraudCheckConnectorData, call_connector_action: payments::CallConnectorAction, _merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 15ceb9b1da..414bb00e76 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -110,6 +110,7 @@ pub async fn revoke_mandate( > = connector_data.connector.get_connector_integration(); let router_data = utils::construct_mandate_revoke_router_data( + &state, merchant_connector_account, &merchant_account, mandate.clone(), diff --git a/crates/router/src/core/mandate/utils.rs b/crates/router/src/core/mandate/utils.rs index 5418d7b7a7..95736604fc 100644 --- a/crates/router/src/core/mandate/utils.rs +++ b/crates/router/src/core/mandate/utils.rs @@ -7,6 +7,7 @@ use error_stack::ResultExt; use crate::{ core::{errors, payments::helpers}, types::{self, domain, PaymentAddress}, + SessionState, }; const IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW: &str = @@ -16,6 +17,7 @@ const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW: &str = "irrelevant_connector_request_reference_id_in_mandate_revoke_flow"; pub async fn construct_mandate_revoke_router_data( + state: &SessionState, merchant_connector_account: helpers::MerchantConnectorAccountType, merchant_account: &domain::MerchantAccount, mandate: Mandate, @@ -28,6 +30,7 @@ pub async fn construct_mandate_revoke_router_data( flow: PhantomData, merchant_id: merchant_account.get_id().clone(), customer_id: Some(mandate.customer_id), + tenant_id: state.tenant.tenant_id.clone(), connector_customer: None, connector: mandate.connector, payment_id: mandate diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 498a00c240..477803c92a 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -6,7 +6,7 @@ use api_models::{ }; use common_utils::{ consts::{DEFAULT_LOCALE, DEFAULT_SESSION_EXPIRY}, - ext_traits::{AsyncExt, OptionExt, ValueExt}, + ext_traits::{OptionExt, ValueExt}, types::{AmountConvertor, StringMajorUnitForCore}, }; use error_stack::{report, ResultExt}; @@ -28,9 +28,8 @@ use crate::{ }, errors::RouterResponse, get_payment_link_config_value, get_payment_link_config_value_based_on_priority, - headers::ACCEPT_LANGUAGE, routes::SessionState, - services::{self, authentication::get_header_value_by_key}, + services, types::{ api::payment_link::PaymentLinkResponseExt, domain, @@ -70,7 +69,6 @@ pub async fn form_payment_link_data( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - locale: Option, ) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> { todo!() } @@ -82,7 +80,6 @@ pub async fn form_payment_link_data( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - locale: Option, ) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> { let db = &*state.store; let key_manager_state = &state.into(); @@ -242,7 +239,7 @@ pub async fn form_payment_link_data( redirect: false, theme: payment_link_config.theme.clone(), return_url: return_url.clone(), - locale: locale.clone(), + locale: Some(state.clone().locale), transaction_details: payment_link_config.transaction_details.clone(), unified_code: payment_attempt.unified_code, unified_message: payment_attempt.unified_message, @@ -273,7 +270,7 @@ pub async fn form_payment_link_data( 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, + locale: Some(state.clone().locale), transaction_details: payment_link_config.transaction_details.clone(), background_image: payment_link_config.background_image.clone(), details_layout: payment_link_config.details_layout, @@ -296,17 +293,9 @@ pub async fn initiate_secure_payment_link_flow( payment_id: common_utils::id_type::PaymentId, request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); - let (payment_link, payment_link_details, payment_link_config) = form_payment_link_data( - &state, - merchant_account, - key_store, - merchant_id, - payment_id, - locale, - ) - .await?; + let (payment_link, payment_link_details, payment_link_config) = + form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id) + .await?; validator::validate_secure_payment_link_render_request( request_headers, @@ -396,19 +385,10 @@ pub async fn initiate_payment_link_flow( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); - let (_, payment_details, payment_link_config) = form_payment_link_data( - &state, - merchant_account, - key_store, - merchant_id, - payment_id, - locale, - ) - .await?; + let (_, payment_details, payment_link_config) = + form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id) + .await?; let css_script = get_color_scheme_css(&payment_link_config); let js_script = get_js_script(&payment_details)?; @@ -727,7 +707,6 @@ pub async fn get_payment_link_status( _key_store: domain::MerchantKeyStore, _merchant_id: common_utils::id_type::MerchantId, _payment_id: common_utils::id_type::PaymentId, - _request_headers: &header::HeaderMap, ) -> RouterResponse { todo!() } @@ -739,10 +718,7 @@ pub async fn get_payment_link_status( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); let db = &*state.store; let key_manager_state = &(&state).into(); @@ -858,19 +834,14 @@ pub async fn get_payment_link_status( consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), ) }; - let unified_translated_message = locale - .as_ref() - .async_and_then(|locale_str| async { - helpers::get_unified_translation( - &state, - unified_code.to_owned(), - unified_message.to_owned(), - locale_str.to_owned(), - ) - .await - }) - .await - .or(Some(unified_message)); + let unified_translated_message = helpers::get_unified_translation( + &state, + unified_code.to_owned(), + unified_message.to_owned(), + state.locale.clone(), + ) + .await + .or(Some(unified_message)); let payment_details = api_models::payments::PaymentLinkStatusDetails { amount, @@ -885,7 +856,7 @@ pub async fn get_payment_link_status( redirect: true, theme: payment_link_config.theme.clone(), return_url, - locale, + locale: Some(state.locale.clone()), transaction_details: payment_link_config.transaction_details, unified_code: Some(unified_code), unified_message: unified_translated_message, diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index d45a72b5e5..bc851bbeb1 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -27,7 +27,12 @@ function initializeSDK() { // @ts-ignore hyper = window.Hyper(pub_key, { isPreloadEnabled: false, + // TODO: Remove in next deployment shouldUseTopRedirection: true, + redirectionFlags: { + shouldRemoveBeforeUnloadEvents: true, + shouldUseTopRedirection: true, + } }); // @ts-ignore widgets = hyper.widgets({ diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 4bddc6904b..290df7d120 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -48,7 +48,12 @@ if (!isFramed) { // @ts-ignore hyper = window.Hyper(pub_key, { isPreloadEnabled: false, + // TODO: Remove in next deployment shouldUseTopRedirection: true, + redirectionFlags: { + shouldRemoveBeforeUnloadEvents: true, + shouldUseTopRedirection: true, + } }); // @ts-ignore widgets = hyper.widgets({ diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 6cdadf0732..25ece15d47 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1025,15 +1025,12 @@ pub async fn payment_method_intent_confirm( req: api::PaymentMethodIntentConfirm, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - pm_id: String, + pm_id: id_type::GlobalPaymentMethodId, ) -> RouterResponse { let key_manager_state = &(state).into(); req.validate()?; let db = &*state.store; - let pm_id = id_type::GlobalPaymentMethodId::generate_from_string(pm_id) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to generate GlobalPaymentMethodId")?; let payment_method = db .find_payment_method( @@ -1131,6 +1128,199 @@ pub async fn payment_method_intent_confirm( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(feature = "v2")] +trait PerformFilteringOnEnabledPaymentMethods { + fn perform_filtering(self) -> FilteredPaymentMethodsEnabled; +} + +#[cfg(feature = "v2")] +impl PerformFilteringOnEnabledPaymentMethods + for hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled +{ + fn perform_filtering(self) -> FilteredPaymentMethodsEnabled { + FilteredPaymentMethodsEnabled(self.payment_methods_enabled) + } +} + +#[cfg(feature = "v2")] +/// Container for the inputs required for the required fields +struct RequiredFieldsInput { + required_fields_config: settings::RequiredFields, +} + +#[cfg(feature = "v2")] +impl RequiredFieldsInput { + fn new(required_fields_config: settings::RequiredFields) -> Self { + Self { + required_fields_config, + } + } +} + +#[cfg(feature = "v2")] +/// Container for the filtered payment methods +struct FilteredPaymentMethodsEnabled( + Vec, +); + +#[cfg(feature = "v2")] +trait GetRequiredFields { + fn get_required_fields( + &self, + payment_method_enabled: &hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector, + ) -> Option<&settings::RequiredFieldFinal>; +} + +#[cfg(feature = "v2")] +impl GetRequiredFields for settings::RequiredFields { + fn get_required_fields( + &self, + payment_method_enabled: &hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector, + ) -> Option<&settings::RequiredFieldFinal> { + self.0 + .get(&payment_method_enabled.payment_method) + .and_then(|required_fields_for_payment_method| { + required_fields_for_payment_method.0.get( + &payment_method_enabled + .payment_methods_enabled + .payment_method_subtype, + ) + }) + .map(|connector_fields| &connector_fields.fields) + .and_then(|connector_hashmap| connector_hashmap.get(&payment_method_enabled.connector)) + } +} + +#[cfg(feature = "v2")] +impl FilteredPaymentMethodsEnabled { + fn get_required_fields( + self, + input: RequiredFieldsInput, + ) -> RequiredFieldsForEnabledPaymentMethodTypes { + let required_fields_config = input.required_fields_config; + + let required_fields_info = self + .0 + .into_iter() + .map(|payment_methods_enabled| { + let required_fields = + required_fields_config.get_required_fields(&payment_methods_enabled); + + let required_fields = required_fields + .map(|required_fields| { + let common_required_fields = required_fields + .common + .iter() + .flatten() + .map(ToOwned::to_owned); + + // Collect mandate required fields because this is for zero auth mandates only + let mandate_required_fields = required_fields + .mandate + .iter() + .flatten() + .map(ToOwned::to_owned); + + // Combine both common and mandate required fields + common_required_fields + .chain(mandate_required_fields) + .collect::>() + }) + .unwrap_or_default(); + + RequiredFieldsForEnabledPaymentMethod { + required_fields, + payment_method_type: payment_methods_enabled.payment_method, + payment_method_subtype: payment_methods_enabled + .payment_methods_enabled + .payment_method_subtype, + } + }) + .collect(); + + RequiredFieldsForEnabledPaymentMethodTypes(required_fields_info) + } +} + +#[cfg(feature = "v2")] +/// Element container to hold the filtered payment methods with required fields +struct RequiredFieldsForEnabledPaymentMethod { + required_fields: Vec, + payment_method_subtype: common_enums::PaymentMethodType, + payment_method_type: common_enums::PaymentMethod, +} + +#[cfg(feature = "v2")] +/// Container to hold the filtered payment methods enabled with required fields +struct RequiredFieldsForEnabledPaymentMethodTypes(Vec); + +#[cfg(feature = "v2")] +impl RequiredFieldsForEnabledPaymentMethodTypes { + fn generate_response(self) -> payment_methods::PaymentMethodListResponse { + let response_payment_methods = self + .0 + .into_iter() + .map( + |payment_methods_enabled| payment_methods::ResponsePaymentMethodTypes { + payment_method_type: payment_methods_enabled.payment_method_type, + payment_method_subtype: payment_methods_enabled.payment_method_subtype, + required_fields: payment_methods_enabled.required_fields, + extra_information: None, + }, + ) + .collect(); + + payment_methods::PaymentMethodListResponse { + payment_methods_enabled: response_payment_methods, + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn list_payment_methods_enabled( + state: SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, + payment_method_id: id_type::GlobalPaymentMethodId, +) -> RouterResponse { + let key_manager_state = &(&state).into(); + + let db = &*state.store; + + db.find_payment_method( + key_manager_state, + &key_store, + &payment_method_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) + .attach_printable("Unable to find payment method")?; + + let payment_connector_accounts = db + .list_enabled_connector_accounts_by_profile_id( + key_manager_state, + profile.get_id(), + &key_store, + common_enums::ConnectorType::PaymentProcessor, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error when fetching merchant connector accounts")?; + + let response = + hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts) + .perform_filtering() + .get_required_fields(RequiredFieldsInput::new(state.conf.required_fields.clone())) + .generate_response(); + + Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( + response, + )) +} + #[cfg(all( feature = "v2", feature = "payment_methods_v2", @@ -1787,7 +1977,7 @@ pub async fn retrieve_payment_method( let resp = api::PaymentMethodResponse { merchant_id: payment_method.merchant_id.to_owned(), customer_id: payment_method.customer_id.to_owned(), - payment_method_id: payment_method.id.get_string_repr().to_string(), + id: payment_method.id.to_owned(), payment_method_type: payment_method.get_payment_method_type(), payment_method_subtype: payment_method.get_payment_method_subtype(), created: Some(payment_method.created_at), @@ -1955,9 +2145,7 @@ pub async fn delete_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to delete payment method from vault")?; - let response = api::PaymentMethodDeleteResponse { - payment_method_id: pm_id.get_string_repr().to_string(), - }; + let response = api::PaymentMethodDeleteResponse { id: pm_id }; Ok(services::ApplicationResponse::Json(response)) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 164c0e9557..5bd2c07073 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -13,10 +13,9 @@ use api_models::{ enums as api_enums, payment_methods::{ BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, - CountryCodeWithName, CustomerDefaultPaymentMethodResponse, ListCountriesCurrenciesRequest, - ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentExperienceTypes, - PaymentMethodsData, RequestPaymentMethodTypes, RequiredFieldInfo, - ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes, + CountryCodeWithName, ListCountriesCurrenciesRequest, ListCountriesCurrenciesResponse, + MaskedBankDetails, PaymentExperienceTypes, PaymentMethodsData, RequestPaymentMethodTypes, + RequiredFieldInfo, ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes, ResponsePaymentMethodsEnabled, }, payments::BankCodeResponse, @@ -2304,40 +2303,38 @@ pub fn validate_payment_method_update( card_updation_obj .card_exp_month .map(|exp_month| exp_month.expose()) - .map_or(false, |new_exp_month| { + .is_some_and(|new_exp_month| { existing_card_data .expiry_month .map(|exp_month| exp_month.expose()) - .map_or(true, |old_exp_month| new_exp_month != old_exp_month) + != Some(new_exp_month) }) || card_updation_obj .card_exp_year .map(|exp_year| exp_year.expose()) - .map_or(false, |new_exp_year| { + .is_some_and(|new_exp_year| { existing_card_data .expiry_year .map(|exp_year| exp_year.expose()) - .map_or(true, |old_exp_year| new_exp_year != old_exp_year) + != Some(new_exp_year) }) || card_updation_obj .card_holder_name .map(|name| name.expose()) - .map_or(false, |new_card_holder_name| { + .is_some_and(|new_card_holder_name| { existing_card_data .card_holder_name .map(|name| name.expose()) - .map_or(true, |old_card_holder_name| { - new_card_holder_name != old_card_holder_name - }) + != Some(new_card_holder_name) }) || card_updation_obj .nick_name .map(|nick_name| nick_name.expose()) - .map_or(false, |new_nick_name| { + .is_some_and(|new_nick_name| { existing_card_data .nick_name .map(|nick_name| nick_name.expose()) - .map_or(true, |old_nick_name| new_nick_name != old_nick_name) + != Some(new_nick_name) }) } @@ -3470,12 +3467,14 @@ pub async fn list_payment_methods( .any(|mca| mca.payment_method == enums::PaymentMethod::Wallet); if wallet_pm_exists { match db - .find_payment_method_by_customer_id_merchant_id_list( + .find_payment_method_by_customer_id_merchant_id_status( &((&state).into()), &key_store, - &customer.customer_id, - merchant_account.get_id(), + &customer.customer_id, + merchant_account.get_id(), + common_enums::PaymentMethodStatus::Active, None, + merchant_account.storage_scheme, ) .await { @@ -4211,9 +4210,9 @@ pub async fn list_payment_methods( } }); - let is_tax_connector_enabled = business_profile.as_ref().map_or(false, |business_profile| { - business_profile.get_is_tax_connector_enabled() - }); + let is_tax_connector_enabled = business_profile + .as_ref() + .is_some_and(|business_profile| business_profile.get_is_tax_connector_enabled()); Ok(services::ApplicationResponse::Json( api::PaymentMethodListResponse { @@ -5511,18 +5510,6 @@ pub async fn get_bank_account_connector_details( } } -#[cfg(all(feature = "v2", feature = "customer_v2"))] -pub async fn set_default_payment_method( - _state: &routes::SessionState, - _merchant_id: &id_type::MerchantId, - _key_store: domain::MerchantKeyStore, - _customer_id: &id_type::CustomerId, - _payment_method_id: String, - _storage_scheme: MerchantStorageScheme, -) -> errors::RouterResponse { - todo!() -} - #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] pub async fn set_default_payment_method( state: &routes::SessionState, @@ -5531,7 +5518,7 @@ pub async fn set_default_payment_method( customer_id: &id_type::CustomerId, payment_method_id: String, storage_scheme: MerchantStorageScheme, -) -> errors::RouterResponse { +) -> errors::RouterResponse { let db = &*state.store; let key_manager_state = &state.into(); // check for the customer @@ -5599,7 +5586,7 @@ pub async fn set_default_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update the default payment method id for the customer")?; - let resp = CustomerDefaultPaymentMethodResponse { + let resp = api_models::payment_methods::CustomerDefaultPaymentMethodResponse { default_payment_method_id: updated_customer_details.default_payment_method_id, customer_id, payment_method_type: payment_method.get_payment_method_subtype(), diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index ce95096a0f..4a43eef1ed 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -567,7 +567,7 @@ pub fn generate_payment_method_response( let resp = api::PaymentMethodResponse { merchant_id: pm.merchant_id.to_owned(), customer_id: pm.customer_id.to_owned(), - payment_method_id: pm.id.get_string_repr().to_owned(), + id: pm.id.to_owned(), payment_method_type: pm.get_payment_method_type(), payment_method_subtype: pm.get_payment_method_subtype(), created: Some(pm.created_at), diff --git a/crates/router/src/core/payment_methods/utils.rs b/crates/router/src/core/payment_methods/utils.rs index d7e83cb5bf..5885b830e7 100644 --- a/crates/router/src/core/payment_methods/utils.rs +++ b/crates/router/src/core/payment_methods/utils.rs @@ -41,7 +41,7 @@ pub fn make_pm_graph( Ok(()) } -pub async fn get_merchant_pm_filter_graph<'a>( +pub async fn get_merchant_pm_filter_graph( state: &SessionState, key: &str, ) -> Option>> { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index cbf2cdea53..ceeb384ed2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5,6 +5,7 @@ pub mod customers; pub mod flows; pub mod helpers; pub mod operations; + #[cfg(feature = "retry")] pub mod retry; pub mod routing; @@ -33,14 +34,14 @@ pub use common_enums::enums::CallConnectorAction; use common_utils::{ ext_traits::{AsyncExt, StringExt}, id_type, pii, - types::{MinorUnit, Surcharge}, + types::{AmountConvertor, MinorUnit, Surcharge}, }; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; use events::EventInfo; use futures::future::join_all; use helpers::{decrypt_paze_token, ApplePayData}; -use hyperswitch_domain_models::payments::payment_intent::CustomerData; +use hyperswitch_domain_models::payments::{payment_intent::CustomerData, ClickToPayMetaData}; #[cfg(feature = "v2")] use hyperswitch_domain_models::payments::{ PaymentCaptureData, PaymentConfirmData, PaymentIntentData, PaymentStatusData, @@ -380,29 +381,38 @@ where should_continue_capture, ); - operation - .to_domain()? - .call_external_three_ds_authentication_if_eligible( - state, - &mut payment_data, - &mut should_continue_transaction, - &connector_details, - &business_profile, - &key_store, - mandate_type, - ) - .await?; - operation - .to_domain()? - .call_unified_authentication_service_if_eligible( - state, - &mut payment_data, - &mut should_continue_transaction, - &connector_details, - &business_profile, - &key_store, - ) - .await?; + if helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), state) + .await? + { + operation + .to_domain()? + .call_unified_authentication_service_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + ) + .await?; + } else { + logger::info!( + "skipping authentication service call since the merchant is not eligible." + ); + + operation + .to_domain()? + .call_external_three_ds_authentication_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + mandate_type, + ) + .await?; + }; operation .to_domain()? @@ -3432,29 +3442,21 @@ pub async fn get_session_token_for_click_to_pay( state: &SessionState, merchant_id: &id_type::MerchantId, key_store: &domain::MerchantKeyStore, - authentication_product_ids: serde_json::Value, + authentication_product_ids: common_types::payments::AuthenticationConnectorAccountMap, payment_intent: &hyperswitch_domain_models::payments::PaymentIntent, ) -> RouterResult { - use common_utils::{id_type::MerchantConnectorAccountId, types::AmountConvertor}; - use hyperswitch_domain_models::payments::{payment_intent::CustomerData, ClickToPayMetaData}; - - use crate::consts::CLICK_TO_PAY; - - let mca_ids: HashMap = authentication_product_ids - .parse_value("HashMap") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while parsing authentication product ids")?; - let click_to_pay_mca_id = mca_ids - .get(CLICK_TO_PAY) - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while getting click_to_pay mca_id from business profile")?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "authentication_product_ids", + })?; let key_manager_state = &(state).into(); let merchant_connector_account = state .store .find_by_merchant_connector_account_merchant_id_merchant_connector_id( key_manager_state, merchant_id, - click_to_pay_mca_id, + &click_to_pay_mca_id, key_store, ) .await @@ -3473,8 +3475,8 @@ pub async fn get_session_token_for_click_to_pay( let required_amount_type = common_utils::types::StringMajorUnitForConnector; let transaction_amount = required_amount_type .convert(payment_intent.amount, transaction_currency) - .change_context(errors::ApiErrorResponse::PreconditionFailed { - message: "Failed to convert amount to string major unit for clickToPay".to_string(), + .change_context(errors::ApiErrorResponse::AmountConversionFailed { + amount_type: "string major unit", })?; let customer_details_value = payment_intent @@ -6564,8 +6566,17 @@ pub async fn payment_external_authentication( &payment_attempt.clone(), payment_connector_name, )); - let webhook_url = - helpers::create_webhook_url(&state.base_url, merchant_id, &authentication_connector); + let mca_id_option = merchant_connector_account.get_mca_id(); // Bind temporary value + let merchant_connector_account_id_or_connector_name = mca_id_option + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(&authentication_connector); + + let webhook_url = helpers::create_webhook_url( + &state.base_url, + merchant_id, + merchant_connector_account_id_or_connector_name, + ); let authentication_details = business_profile .authentication_connector_details diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index c96b8f915b..28febe8b0b 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -699,10 +699,8 @@ default_imp_for_new_connector_integration_payment!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -724,7 +722,6 @@ default_imp_for_new_connector_integration_payment!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise, connector::Plaid @@ -751,10 +748,8 @@ default_imp_for_new_connector_integration_refund!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -776,7 +771,6 @@ default_imp_for_new_connector_integration_refund!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -797,10 +791,8 @@ default_imp_for_new_connector_integration_connector_access_token!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -822,7 +814,6 @@ default_imp_for_new_connector_integration_connector_access_token!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -865,10 +856,8 @@ default_imp_for_new_connector_integration_accept_dispute!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -890,7 +879,6 @@ default_imp_for_new_connector_integration_accept_dispute!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -915,10 +903,8 @@ default_imp_for_new_connector_integration_defend_dispute!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -940,7 +926,6 @@ default_imp_for_new_connector_integration_defend_dispute!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -949,10 +934,8 @@ default_imp_for_new_connector_integration_submit_evidence!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -974,7 +957,6 @@ default_imp_for_new_connector_integration_submit_evidence!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1010,10 +992,8 @@ default_imp_for_new_connector_integration_file_upload!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1035,7 +1015,6 @@ default_imp_for_new_connector_integration_file_upload!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1120,7 +1099,6 @@ default_imp_for_new_connector_integration_payouts!( connector::Tsys, connector::UnifiedAuthenticationService, connector::Volt, - connector::Wellsfargo, connector::Wise, connector::Worldline, connector::Worldpay, @@ -1152,10 +1130,8 @@ default_imp_for_new_connector_integration_payouts_create!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1177,7 +1153,6 @@ default_imp_for_new_connector_integration_payouts_create!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1205,10 +1180,8 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1230,7 +1203,6 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1258,10 +1230,8 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1283,7 +1253,6 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1311,10 +1280,8 @@ default_imp_for_new_connector_integration_payouts_cancel!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1336,7 +1303,6 @@ default_imp_for_new_connector_integration_payouts_cancel!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1364,10 +1330,8 @@ default_imp_for_new_connector_integration_payouts_quote!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1389,7 +1353,6 @@ default_imp_for_new_connector_integration_payouts_quote!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1417,10 +1380,8 @@ default_imp_for_new_connector_integration_payouts_recipient!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1442,7 +1403,6 @@ default_imp_for_new_connector_integration_payouts_recipient!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1470,10 +1430,8 @@ default_imp_for_new_connector_integration_payouts_sync!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1495,7 +1453,6 @@ default_imp_for_new_connector_integration_payouts_sync!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1523,10 +1480,8 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1548,7 +1503,6 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1574,10 +1528,8 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1599,7 +1551,6 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1621,7 +1572,6 @@ default_imp_for_new_connector_integration_frm!( connector::Authorizedotnet, connector::Bambora, connector::Bamboraapac, - connector::Bankofamerica, connector::Billwerk, connector::Bitpay, connector::Bluesnap, @@ -1631,7 +1581,6 @@ default_imp_for_new_connector_integration_frm!( connector::Checkout, connector::Cryptopay, connector::Coinbase, - connector::Cybersource, connector::Deutschebank, connector::Digitalvirgo, connector::Dlocal, @@ -1686,7 +1635,6 @@ default_imp_for_new_connector_integration_frm!( connector::Tsys, connector::UnifiedAuthenticationService, connector::Volt, - connector::Wellsfargo, connector::Wise, connector::Worldline, connector::Worldpay, @@ -1718,10 +1666,8 @@ default_imp_for_new_connector_integration_frm_sale!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1743,7 +1689,6 @@ default_imp_for_new_connector_integration_frm_sale!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1771,10 +1716,8 @@ default_imp_for_new_connector_integration_frm_checkout!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1796,7 +1739,6 @@ default_imp_for_new_connector_integration_frm_checkout!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1824,10 +1766,8 @@ default_imp_for_new_connector_integration_frm_transaction!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1849,7 +1789,6 @@ default_imp_for_new_connector_integration_frm_transaction!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1877,10 +1816,8 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1902,7 +1839,6 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1930,10 +1866,8 @@ default_imp_for_new_connector_integration_frm_record_return!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1955,7 +1889,6 @@ default_imp_for_new_connector_integration_frm_record_return!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -1980,10 +1913,8 @@ default_imp_for_new_connector_integration_revoking_mandates!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2005,7 +1936,6 @@ default_imp_for_new_connector_integration_revoking_mandates!( connector::Stripe, connector::Trustpay, connector::Threedsecureio, - connector::Wellsfargo, connector::Wise, connector::Plaid ); @@ -2129,10 +2059,10 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Tsys, connector::UnifiedAuthenticationService, connector::Volt, - connector::Wellsfargo, connector::Wise, connector::Worldline, connector::Worldpay, + connector::Xendit, connector::Zen, connector::Zsl, connector::Plaid @@ -2168,10 +2098,8 @@ default_imp_for_new_connector_integration_uas!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2194,7 +2122,6 @@ default_imp_for_new_connector_integration_uas!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 9c9d0a454b..9ce3ad6c02 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -215,7 +215,6 @@ default_imp_for_complete_authorize!( connector::Adyenplatform, connector::Aci, connector::Adyen, - connector::Bankofamerica, connector::Checkout, connector::Ebanx, connector::Gpayments, @@ -235,7 +234,6 @@ default_imp_for_complete_authorize!( connector::Threedsecureio, connector::Trustpay, connector::Wise, - connector::Wellsfargo, connector::Wellsfargopayout ); macro_rules! default_imp_for_webhook_source_verification { @@ -269,10 +267,8 @@ default_imp_for_webhook_source_verification!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -294,7 +290,6 @@ default_imp_for_webhook_source_verification!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -331,10 +326,8 @@ default_imp_for_create_customer!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -356,7 +349,6 @@ default_imp_for_create_customer!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -394,8 +386,6 @@ default_imp_for_connector_redirect_response!( connector::Adyenplatform, connector::Aci, connector::Adyen, - connector::Bankofamerica, - connector::Cybersource, connector::Ebanx, connector::Gpayments, connector::Iatapay, @@ -410,7 +400,6 @@ default_imp_for_connector_redirect_response!( connector::Riskified, connector::Signifyd, connector::Threedsecureio, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -507,6 +496,7 @@ default_imp_for_connector_request_id!( connector::Wise, connector::Worldline, connector::Worldpay, + connector::Xendit, connector::Zen, connector::Zsl, connector::CtpMastercard @@ -546,9 +536,7 @@ default_imp_for_accept_dispute!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -571,7 +559,6 @@ default_imp_for_accept_dispute!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -629,9 +616,7 @@ default_imp_for_file_upload!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -653,7 +638,6 @@ default_imp_for_file_upload!( connector::Threedsecureio, connector::Trustpay, connector::Opennode, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -689,9 +673,7 @@ default_imp_for_submit_evidence!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -713,7 +695,6 @@ default_imp_for_submit_evidence!( connector::Threedsecureio, connector::Trustpay, connector::Opennode, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -749,9 +730,7 @@ default_imp_for_defend_dispute!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -774,7 +753,6 @@ default_imp_for_defend_dispute!( connector::Threedsecureio, connector::Trustpay, connector::Opennode, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -825,7 +803,6 @@ default_imp_for_pre_processing_steps!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, connector::Ebanx, @@ -844,7 +821,6 @@ default_imp_for_pre_processing_steps!( connector::Riskified, connector::Signifyd, connector::Threedsecureio, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -864,8 +840,6 @@ impl default_imp_for_post_processing_steps!( connector::Adyenplatform, connector::Adyen, - connector::Bankofamerica, - connector::Cybersource, connector::Nmi, connector::Nuvei, connector::Payme, @@ -891,7 +865,6 @@ default_imp_for_post_processing_steps!( connector::Riskified, connector::Signifyd, connector::Threedsecureio, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -910,7 +883,6 @@ impl Payouts for connector::DummyConnector {} default_imp_for_payouts!( connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, connector::Globalpay, @@ -931,7 +903,6 @@ default_imp_for_payouts!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -967,10 +938,8 @@ default_imp_for_payouts_create!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Globalpay, connector::Gpayments, connector::Iatapay, @@ -990,7 +959,6 @@ default_imp_for_payouts_create!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1027,10 +995,8 @@ default_imp_for_payouts_retrieve!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1052,7 +1018,6 @@ default_imp_for_payouts_retrieve!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1092,10 +1057,8 @@ default_imp_for_payouts_eligibility!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Globalpay, connector::Gpayments, connector::Iatapay, @@ -1117,7 +1080,6 @@ default_imp_for_payouts_eligibility!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1152,7 +1114,6 @@ impl default_imp_for_payouts_fulfill!( connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, connector::Globalpay, @@ -1173,7 +1134,6 @@ default_imp_for_payouts_fulfill!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1209,10 +1169,8 @@ default_imp_for_payouts_cancel!( connector::Adyenplatform, connector::Aci, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Globalpay, connector::Gpayments, connector::Iatapay, @@ -1233,7 +1191,6 @@ default_imp_for_payouts_cancel!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1270,10 +1227,8 @@ default_imp_for_payouts_quote!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Globalpay, connector::Gpayments, connector::Iatapay, @@ -1295,7 +1250,6 @@ default_imp_for_payouts_quote!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1332,10 +1286,8 @@ default_imp_for_payouts_recipient!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Globalpay, connector::Gpayments, connector::Iatapay, @@ -1356,7 +1308,6 @@ default_imp_for_payouts_recipient!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout ); @@ -1396,10 +1347,8 @@ default_imp_for_payouts_recipient_account!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1421,7 +1370,6 @@ default_imp_for_payouts_recipient_account!( connector::Signifyd, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1458,10 +1406,8 @@ default_imp_for_approve!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1484,7 +1430,6 @@ default_imp_for_approve!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1521,10 +1466,8 @@ default_imp_for_reject!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1547,7 +1490,6 @@ default_imp_for_reject!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1644,6 +1586,7 @@ default_imp_for_fraud_check!( connector::Wise, connector::Worldline, connector::Worldpay, + connector::Xendit, connector::Zen, connector::Zsl, connector::CtpMastercard @@ -1683,10 +1626,8 @@ default_imp_for_frm_sale!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1707,7 +1648,6 @@ default_imp_for_frm_sale!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1746,10 +1686,8 @@ default_imp_for_frm_checkout!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1770,7 +1708,6 @@ default_imp_for_frm_checkout!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1809,10 +1746,8 @@ default_imp_for_frm_transaction!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1833,7 +1768,6 @@ default_imp_for_frm_transaction!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1872,10 +1806,8 @@ default_imp_for_frm_fulfillment!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1896,7 +1828,6 @@ default_imp_for_frm_fulfillment!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1935,10 +1866,8 @@ default_imp_for_frm_record_return!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -1959,7 +1888,6 @@ default_imp_for_frm_record_return!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -1996,7 +1924,6 @@ default_imp_for_incremental_authorization!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, connector::Ebanx, @@ -2055,7 +1982,6 @@ default_imp_for_revoking_mandates!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, connector::Ebanx, @@ -2248,6 +2174,7 @@ default_imp_for_connector_authentication!( connector::Wise, connector::Worldline, connector::Worldpay, + connector::Xendit, connector::Zen, connector::Zsl, connector::CtpMastercard @@ -2282,10 +2209,8 @@ default_imp_for_authorize_session_token!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2307,7 +2232,6 @@ default_imp_for_authorize_session_token!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -2342,10 +2266,8 @@ default_imp_for_calculate_tax!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2368,7 +2290,6 @@ default_imp_for_calculate_tax!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -2403,10 +2324,8 @@ default_imp_for_session_update!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2428,7 +2347,6 @@ default_imp_for_session_update!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -2463,10 +2381,8 @@ default_imp_for_post_session_tokens!( connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2488,7 +2404,6 @@ default_imp_for_post_session_tokens!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -2526,10 +2441,8 @@ default_imp_for_uas_pre_authentication!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2552,7 +2465,6 @@ default_imp_for_uas_pre_authentication!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); @@ -2587,10 +2499,8 @@ default_imp_for_uas_post_authentication!( connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Bankofamerica, connector::Braintree, connector::Checkout, - connector::Cybersource, connector::Ebanx, connector::Globalpay, connector::Gpayments, @@ -2613,7 +2523,6 @@ default_imp_for_uas_post_authentication!( connector::Stripe, connector::Threedsecureio, connector::Trustpay, - connector::Wellsfargo, connector::Wellsfargopayout, connector::Wise ); diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 1e3432220a..65688c5d46 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -156,6 +156,7 @@ impl Feature for types::PaymentsSessio /// This function checks if for a given connector, payment_method and payment_method_type, /// the list of required_field_type is present in dynamic fields +#[cfg(feature = "v1")] fn is_dynamic_fields_required( required_fields: &settings::RequiredFields, payment_method: enums::PaymentMethod, @@ -187,6 +188,43 @@ fn is_dynamic_fields_required( .unwrap_or(false) } +/// This function checks if for a given connector, payment_method and payment_method_type, +/// the list of required_field_type is present in dynamic fields +#[cfg(feature = "v2")] +fn is_dynamic_fields_required( + required_fields: &settings::RequiredFields, + payment_method: enums::PaymentMethod, + payment_method_type: enums::PaymentMethodType, + connector: types::Connector, + required_field_type: Vec, +) -> bool { + required_fields + .0 + .get(&payment_method) + .and_then(|pm_type| pm_type.0.get(&payment_method_type)) + .and_then(|required_fields_for_connector| { + required_fields_for_connector.fields.get(&connector) + }) + .map(|required_fields_final| { + required_fields_final + .non_mandate + .iter() + .flatten() + .any(|field_info| required_field_type.contains(&field_info.field_type)) + || required_fields_final + .mandate + .iter() + .flatten() + .any(|field_info| required_field_type.contains(&field_info.field_type)) + || required_fields_final + .common + .iter() + .flatten() + .any(|field_info| required_field_type.contains(&field_info.field_type)) + }) + .unwrap_or(false) +} + fn build_apple_pay_session_request( state: &routes::SessionState, request: payment_types::ApplepaySessionRequest, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index b2c90e03b5..93769c71e6 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, str::FromStr}; +use std::{borrow::Cow, collections::HashSet, str::FromStr}; #[cfg(feature = "v2")] use api_models::ephemeral_key::EphemeralKeyResponse; @@ -13,7 +13,8 @@ use common_utils::id_type::GenerateId; use common_utils::{ crypto::Encryptable, ext_traits::{AsyncExt, ByteSliceExt, Encode, ValueExt}, - fp_utils, generate_id, id_type, + fp_utils, generate_id, + id_type::{self}, new_type::{MaskedIban, MaskedSortCode}, pii, type_name, types::{ @@ -1241,17 +1242,18 @@ pub fn create_authorize_url( } pub fn create_webhook_url( - router_base_url: &String, + router_base_url: &str, merchant_id: &id_type::MerchantId, - connector_name: impl std::fmt::Display, + merchant_connector_id_or_connector_name: &str, ) -> String { format!( "{}/webhooks/{}/{}", router_base_url, merchant_id.get_string_repr(), - connector_name + merchant_connector_id_or_connector_name, ) } + pub fn create_complete_authorize_url( router_base_url: &String, payment_attempt: &PaymentAttempt, @@ -4035,6 +4037,7 @@ pub fn router_data_type_conversion( request, response, merchant_id: router_data.merchant_id, + tenant_id: router_data.tenant_id, address: router_data.address, amount_captured: router_data.amount_captured, minor_amount_captured: router_data.minor_amount_captured, @@ -5108,7 +5111,7 @@ pub fn get_applepay_metadata( }) } -#[cfg(feature = "retry")] +#[cfg(all(feature = "retry", feature = "v1"))] pub async fn get_apple_pay_retryable_connectors( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -6166,6 +6169,7 @@ where Ok(()) } +#[cfg(feature = "v1")] pub async fn validate_merchant_connector_ids_in_connector_mandate_details( state: &SessionState, key_store: &domain::MerchantKeyStore, @@ -6268,3 +6272,120 @@ pub fn validate_platform_fees_for_marketplace( } Ok(()) } + +pub async fn is_merchant_eligible_authentication_service( + merchant_id: &id_type::MerchantId, + state: &SessionState, +) -> RouterResult { + let merchants_eligible_for_authentication_service = state + .store + .as_ref() + .find_config_by_key_unwrap_or( + consts::AUTHENTICATION_SERVICE_ELIGIBLE_CONFIG, + Some("[]".to_string()), + ) + .await; + + let auth_eligible_array: Vec = match merchants_eligible_for_authentication_service { + Ok(config) => serde_json::from_str(&config.config) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to parse authentication service config")?, + Err(err) => { + logger::error!( + "Error fetching authentication service enabled merchant config {:?}", + err + ); + Vec::new() + } + }; + + Ok(auth_eligible_array.contains(&merchant_id.get_string_repr().to_owned())) +} + +#[cfg(feature = "v1")] +pub async fn validate_allowed_payment_method_types_request( + state: &SessionState, + profile_id: &id_type::ProfileId, + merchant_account: &domain::MerchantAccount, + merchant_key_store: &domain::MerchantKeyStore, + allowed_payment_method_types: Option>, +) -> CustomResult<(), errors::ApiErrorResponse> { + if let Some(allowed_payment_method_types) = allowed_payment_method_types { + let db = &*state.store; + let all_connector_accounts = db + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + &state.into(), + merchant_account.get_id(), + false, + merchant_key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch merchant connector account for given merchant id")?; + + let filtered_connector_accounts = filter_mca_based_on_profile_and_connector_type( + all_connector_accounts, + profile_id, + ConnectorType::PaymentProcessor, + ); + + let supporting_payment_method_types: HashSet<_> = filtered_connector_accounts + .iter() + .flat_map(|connector_account| { + connector_account + .payment_methods_enabled + .clone() + .unwrap_or_default() + .into_iter() + .map(|payment_methods_enabled| { + payment_methods_enabled + .parse_value::( + "payment_methods_enabled", + ) + }) + .filter_map(|parsed_payment_method_result| { + parsed_payment_method_result + .inspect_err(|err| { + logger::error!( + "Unable to deserialize payment methods enabled: {:?}", + err + ); + }) + .ok() + }) + .flat_map(|parsed_payment_methods_enabled| { + parsed_payment_methods_enabled + .payment_method_types + .unwrap_or_default() + .into_iter() + .map(|payment_method_type| payment_method_type.payment_method_type) + }) + }) + .collect(); + + let unsupported_payment_methods: Vec<_> = allowed_payment_method_types + .iter() + .filter(|allowed_pmt| !supporting_payment_method_types.contains(allowed_pmt)) + .collect(); + + if !unsupported_payment_methods.is_empty() { + metrics::PAYMENT_METHOD_TYPES_MISCONFIGURATION_METRIC.add( + 1, + router_env::metric_attributes!(("merchant_id", merchant_account.get_id().clone())), + ); + } + + fp_utils::when( + unsupported_payment_methods.len() == allowed_payment_method_types.len(), + || { + Err(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration) + .attach_printable(format!( + "None of the allowed payment method types {:?} are configured for this merchant connector account.", + allowed_payment_method_types + )) + }, + )?; + } + + Ok(()) +} diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 4adb940341..0a264f24e9 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -300,6 +300,7 @@ pub trait Domain: Send + Sync { Ok(()) } + #[allow(clippy::too_many_arguments)] async fn call_unified_authentication_service_if_eligible<'a>( &'a self, _state: &SessionState, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index c309685a6a..a87fab9b17 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -41,7 +41,7 @@ use crate::{ }, unified_authentication_service::{ self as uas_utils, - types::{ClickToPay, UnifiedAuthenticationService, CTP_MASTERCARD}, + types::{ClickToPay, UnifiedAuthenticationService}, }, utils as core_utils, }, @@ -1047,21 +1047,42 @@ impl Domain> for business_profile: &domain::Profile, key_store: &domain::MerchantKeyStore, ) -> CustomResult<(), errors::ApiErrorResponse> { + let authentication_product_ids = business_profile + .authentication_product_ids + .clone() + .ok_or(errors::ApiErrorResponse::PreconditionFailed { + message: "authentication_product_ids is not configured in business profile" + .to_string(), + })?; + if let Some(payment_method) = payment_data.payment_attempt.payment_method { if payment_method == storage_enums::PaymentMethod::Card && business_profile.is_click_to_pay_enabled + && payment_data.service_details.is_some() { - let connector_name = CTP_MASTERCARD; // since the above checks satisfies the connector should be click to pay hence hardcoded the connector name - let connector_mca = helpers::get_merchant_connector_account( - state, - &business_profile.merchant_id, - None, - key_store, - business_profile.get_id(), - connector_name, - None, - ) - .await?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "authentication_product_ids", + })?; + + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; + + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; let authentication_id = common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); @@ -1072,21 +1093,13 @@ impl Domain> for }, )?; - let connector_transaction_id = connector_mca - .clone() - .get_mca_id() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Error while finding mca_id from merchant_connector_account", - )?; - ClickToPay::pre_authentication( state, key_store, business_profile, payment_data, &connector_mca, - connector_name, + &connector_mca.connector_name, &authentication_id, payment_method, ) @@ -1100,7 +1113,7 @@ impl Domain> for business_profile, payment_data, &connector_mca, - connector_name, + &connector_mca.connector_name, payment_method, ) .await?; @@ -1144,10 +1157,10 @@ impl Domain> for uas_utils::create_new_authentication( state, payment_data.payment_attempt.merchant_id.clone(), - connector_name.to_string(), + connector_mca.connector_name.to_string(), business_profile.get_id().clone(), Some(payment_data.payment_intent.get_id().clone()), - connector_transaction_id, + click_to_pay_mca_id.to_owned(), &authentication_id, payment_data.service_details.clone(), authentication_status, @@ -1155,6 +1168,11 @@ impl Domain> for .await?; } } + logger::info!( + payment_method=?payment_data.payment_attempt.payment_method, + click_to_pay_enabled=?business_profile.is_click_to_pay_enabled, + "skipping unified authentication service call since payment conditions are not satisfied" + ); Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index b7b3420987..2bb751d4e1 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -181,6 +181,15 @@ impl GetTracker, api::PaymentsRequest> ) .await?; + helpers::validate_allowed_payment_method_types_request( + state, + &profile_id, + merchant_account, + merchant_key_store, + request.allowed_payment_method_types.clone(), + ) + .await?; + let customer_details = helpers::get_customer_details_from_request(request); let shipping_address = helpers::create_or_find_address_for_payment_by_request( @@ -1193,7 +1202,7 @@ impl PaymentCreate { payment_id.get_attempt_id(1) }; - if request.mandate_data.as_ref().map_or(false, |mandate_data| { + if request.mandate_data.as_ref().is_some_and(|mandate_data| { mandate_data.update_mandate_id.is_some() && mandate_data.mandate_type.is_some() }) { Err(errors::ApiErrorResponse::InvalidRequestData {message:"Only one field out of 'mandate_type' and 'update_mandate_id' was expected, found both".to_string()})? diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index fbc2d2ee46..9692b1ca8a 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1971,7 +1971,9 @@ async fn payment_response_update_tracker( #[cfg(all(feature = "v1", feature = "dynamic_routing"))] { - if business_profile.dynamic_routing_algorithm.is_some() { + if payment_intent.status.is_in_terminal_state() + && business_profile.dynamic_routing_algorithm.is_some() + { let state = state.clone(); let business_profile = business_profile.clone(); let payment_attempt = payment_attempt.clone(); diff --git a/crates/router/src/core/payments/payment_methods.rs b/crates/router/src/core/payments/payment_methods.rs index 6ed436ae3c..df100d2e9b 100644 --- a/crates/router/src/core/payments/payment_methods.rs +++ b/crates/router/src/core/payments/payment_methods.rs @@ -34,7 +34,7 @@ pub async fn list_payment_methods( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - validate_payment_status(payment_intent.status)?; + validate_payment_status_for_payment_method_list(payment_intent.status)?; let client_secret = header_payload .client_secret @@ -108,8 +108,7 @@ impl FilteredPaymentMethodsEnabled { /// Element container to hold the filtered payment methods with required fields struct RequiredFieldsForEnabledPaymentMethod { - required_field: - Option>, + required_field: Option>, payment_method_subtype: common_enums::PaymentMethodType, payment_method_type: common_enums::PaymentMethod, } @@ -119,8 +118,7 @@ struct RequiredFieldsForEnabledPaymentMethodTypes(Vec>, + required_field: Option>, payment_method_subtype: common_enums::PaymentMethodType, payment_method_type: common_enums::PaymentMethod, surcharge: Option, @@ -137,7 +135,7 @@ impl RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes { .0 .into_iter() .map(|payment_methods_enabled| { - api_models::payment_methods::ResponsePaymentMethodTypes { + api_models::payments::ResponsePaymentMethodTypesForPayments { payment_method_type: payment_methods_enabled.payment_method_type, payment_method_subtype: payment_methods_enabled.payment_method_subtype, required_fields: payment_methods_enabled.required_field, @@ -188,7 +186,7 @@ impl PerformFilteringOnPaymentMethodsEnabled } /// Validate if payment methods list can be performed on the current status of payment intent -fn validate_payment_status( +fn validate_payment_status_for_payment_method_list( intent_status: common_enums::IntentStatus, ) -> Result<(), errors::ApiErrorResponse> { match intent_status { diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 8f3f40edf0..271a567928 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -37,6 +37,8 @@ use rand::{ distributions::{self, Distribution}, SeedableRng, }; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use router_env::{instrument, tracing}; use rustc_hash::FxHashMap; use storage_impl::redis::cache::{CacheKey, CGRAPH_CACHE, ROUTING_CACHE}; @@ -554,7 +556,7 @@ pub fn perform_volume_split( } #[cfg(feature = "v1")] -pub async fn get_merchant_cgraph<'a>( +pub async fn get_merchant_cgraph( state: &SessionState, key_store: &domain::MerchantKeyStore, profile_id: &common_utils::id_type::ProfileId, @@ -601,7 +603,7 @@ pub async fn get_merchant_cgraph<'a>( } #[cfg(feature = "v1")] -pub async fn refresh_cgraph_cache<'a>( +pub async fn refresh_cgraph_cache( state: &SessionState, key_store: &domain::MerchantKeyStore, key: String, @@ -1281,6 +1283,7 @@ pub fn make_dsl_input_for_surcharge( /// success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] pub async fn perform_success_based_routing( state: &SessionState, routable_connectors: Vec, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 79bcfb6a49..80c10a294e 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -122,6 +122,7 @@ where .payment_id .get_string_repr() .to_owned(), + tenant_id: state.tenant.tenant_id.clone(), attempt_id: payment_data.payment_attempt.get_id().to_owned(), status: payment_data.payment_attempt.status, payment_method: diesel_models::enums::PaymentMethod::default(), @@ -224,7 +225,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_id, + merchant_connector_account.get_id().get_string_repr(), )); let router_return_url = payment_data @@ -240,6 +241,17 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .map(|id| id.get_string_repr().to_owned()) .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); + let email = customer + .as_ref() + .and_then(|customer| customer.email.clone()) + .map(pii::Email::from); + + let browser_info = payment_data + .payment_attempt + .browser_info + .clone() + .map(types::BrowserInformation::from); + // TODO: few fields are repeated in both routerdata and request let request = types::PaymentsAuthorizeData { payment_method_data: payment_data @@ -261,8 +273,8 @@ pub async fn construct_payment_router_data_for_authorize<'a>( minor_amount: payment_data.payment_attempt.amount_details.get_net_amount(), order_tax_amount: None, currency: payment_data.payment_intent.amount_details.currency, - browser_info: None, - email: None, + browser_info, + email, customer_name: None, payment_experience: None, order_details: None, @@ -301,6 +313,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.get_id().clone(), + tenant_id: state.tenant.tenant_id.clone(), // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. customer_id, connector: connector_id.to_owned(), @@ -464,6 +477,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. customer_id, connector: connector_id.to_owned(), + tenant_id: state.tenant.tenant_id.clone(), // TODO: evaluate why we need payment id at the connector level. We already have connector reference id payment_id: payment_data .payment_attempt @@ -599,6 +613,7 @@ pub async fn construct_router_data_for_psync<'a>( merchant_id: merchant_account.get_id().clone(), // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. customer_id, + tenant_id: state.tenant.tenant_id.clone(), connector: connector_id.to_owned(), // TODO: evaluate why we need payment id at the connector level. We already have connector reference id payment_id: payment_intent.id.get_string_repr().to_owned(), @@ -662,7 +677,7 @@ pub async fn construct_router_data_for_psync<'a>( #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn construct_payment_router_data_for_sdk_session<'a>( - _state: &'a SessionState, + state: &'a SessionState, payment_data: hyperswitch_domain_models::payments::PaymentIntentData, connector_id: &str, merchant_account: &domain::MerchantAccount, @@ -756,6 +771,7 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. customer_id, connector: connector_id.to_owned(), + tenant_id: state.tenant.tenant_id.clone(), // TODO: evaluate why we need payment id at the connector level. We already have connector reference id payment_id: payment_data.payment_intent.id.get_string_repr().to_owned(), // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id @@ -944,6 +960,7 @@ where flow: PhantomData, merchant_id: merchant_account.get_id().clone(), customer_id, + tenant_id: state.tenant.tenant_id.clone(), connector: connector_id.to_owned(), payment_id: payment_data .payment_attempt @@ -2745,11 +2762,17 @@ impl TryFrom> for types::PaymentsAuthoriz attempt, connector_name, )); + let merchant_connector_account_id_or_connector_name = payment_data + .payment_attempt + .merchant_connector_id + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_name, + merchant_connector_account_id_or_connector_name, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, @@ -3569,6 +3592,18 @@ impl TryFrom> for types::SetupMandateRequ .map(|customer| customer.clone().into_inner()) }); let amount = payment_data.payment_attempt.get_total_amount(); + let merchant_connector_account_id_or_connector_name = payment_data + .payment_attempt + .merchant_connector_id + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); + let webhook_url = Some(helpers::create_webhook_url( + router_base_url, + &attempt.merchant_id, + merchant_connector_account_id_or_connector_name, + )); + Ok(Self { currency: payment_data.currency, confirm: true, @@ -3598,6 +3633,7 @@ impl TryFrom> for types::SetupMandateRequ ), metadata: payment_data.payment_intent.metadata.clone().map(Into::into), shipping_cost: payment_data.payment_intent.shipping_cost, + webhook_url, }) } } @@ -3764,11 +3800,16 @@ impl TryFrom> for types::PaymentsPreProce .collect::, _>>() }) .transpose()?; - + let merchant_connector_account_id_or_connector_name = payment_data + .payment_attempt + .merchant_connector_id + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_name, + merchant_connector_account_id_or_connector_name, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs index 22354b4c00..e45b4fcead 100644 --- a/crates/router/src/core/payout_link.rs +++ b/crates/router/src/core/payout_link.rs @@ -46,7 +46,6 @@ pub async fn initiate_payout_link( key_store: domain::MerchantKeyStore, req: payouts::PayoutLinkInitiateRequest, request_headers: &header::HeaderMap, - locale: String, ) -> RouterResponse { let db: &dyn StorageInterface = &*state.store; let merchant_id = merchant_account.get_id(); @@ -128,7 +127,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::ExpiredLink(expired_link_data), - locale, + locale: state.locale, }, ))) } @@ -245,7 +244,7 @@ pub async fn initiate_payout_link( enabled_payment_methods_with_required_fields, amount, currency: payout.destination_currency, - locale: locale.clone(), + locale: state.locale.clone(), form_layout: link_data.form_layout, test_mode: link_data.test_mode.unwrap_or(false), }; @@ -270,7 +269,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::PayoutLink(generic_form_data), - locale, + locale: state.locale.clone(), }, ))) } @@ -282,7 +281,7 @@ pub async fn initiate_payout_link( &state, payout_attempt.unified_code.as_ref(), payout_attempt.unified_message.as_ref(), - &locale, + &state.locale.clone(), ) .await?; let js_data = payouts::PayoutLinkStatusDetails { @@ -322,7 +321,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::PayoutLinkStatus(generic_status_data), - locale, + locale: state.locale.clone(), }, ))) } diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index b2d6b2ace4..11be2ebfb5 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -317,7 +317,6 @@ pub async fn payouts_create_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { // Validate create request let (payout_id, payout_method_data, profile_id, customer) = @@ -332,7 +331,7 @@ pub async fn payouts_create_core( &payout_id, &profile_id, payout_method_data.as_ref(), - locale, + &state.locale, customer.as_ref(), ) .await?; @@ -382,7 +381,6 @@ pub async fn payouts_confirm_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -390,7 +388,7 @@ pub async fn payouts_confirm_core( None, &key_store, &payouts::PayoutRequest::PayoutCreateRequest(Box::new(req.to_owned())), - locale, + &state.locale, ) .await?; let payout_attempt = payout_data.payout_attempt.to_owned(); @@ -454,7 +452,6 @@ pub async fn payouts_update_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { let payout_id = req.payout_id.clone().get_required_value("payout_id")?; let mut payout_data = make_payout_data( @@ -463,7 +460,7 @@ pub async fn payouts_update_core( None, &key_store, &payouts::PayoutRequest::PayoutCreateRequest(Box::new(req.to_owned())), - locale, + &state.locale, ) .await?; @@ -539,7 +536,6 @@ pub async fn payouts_retrieve_core( profile_id: Option, key_store: domain::MerchantKeyStore, req: payouts::PayoutRetrieveRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -547,7 +543,7 @@ pub async fn payouts_retrieve_core( profile_id, &key_store, &payouts::PayoutRequest::PayoutRetrieveRequest(req.to_owned()), - locale, + &state.locale, ) .await?; let payout_attempt = payout_data.payout_attempt.to_owned(); @@ -584,7 +580,6 @@ pub async fn payouts_cancel_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutActionRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -592,7 +587,7 @@ pub async fn payouts_cancel_core( None, &key_store, &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()), - locale, + &state.locale, ) .await?; @@ -678,7 +673,6 @@ pub async fn payouts_fulfill_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutActionRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -686,7 +680,7 @@ pub async fn payouts_fulfill_core( None, &key_store, &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()), - locale, + &state.locale, ) .await?; @@ -773,7 +767,6 @@ pub async fn payouts_list_core( _profile_id_list: Option>, _key_store: domain::MerchantKeyStore, _constraints: payouts::PayoutListConstraints, - _locale: &str, ) -> RouterResponse { todo!() } @@ -789,7 +782,6 @@ pub async fn payouts_list_core( profile_id_list: Option>, key_store: domain::MerchantKeyStore, constraints: payouts::PayoutListConstraints, - _locale: &str, ) -> RouterResponse { validator::validate_payout_list_request(&constraints)?; let merchant_id = merchant_account.get_id(); @@ -910,7 +902,6 @@ pub async fn payouts_filtered_list_core( profile_id_list: Option>, key_store: domain::MerchantKeyStore, filters: payouts::PayoutListFilterConstraints, - _locale: &str, ) -> RouterResponse { let limit = &filters.limit; validator::validate_payout_list_request_for_joins(*limit)?; @@ -1014,7 +1005,6 @@ pub async fn payouts_list_available_filters_core( merchant_account: domain::MerchantAccount, profile_id_list: Option>, time_range: common_utils::types::TimeRange, - _locale: &str, ) -> RouterResponse { let db = state.store.as_ref(); let payouts = db @@ -1226,9 +1216,13 @@ pub async fn create_recipient( ); if should_call_connector { // 1. Form router data - let router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Fetch connector integration details let connector_integration: services::BoxedPayoutConnectorIntegrationInterface< @@ -1406,9 +1400,13 @@ pub async fn check_payout_eligibility( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Fetch connector integration details let connector_integration: services::BoxedPayoutConnectorIntegrationInterface< @@ -1604,9 +1602,13 @@ pub async fn create_payout( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let mut router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let mut router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Get/Create access token access_token::create_access_token( @@ -1818,9 +1820,13 @@ pub async fn create_payout_retrieve( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let mut router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let mut router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Get/Create access token access_token::create_access_token( @@ -1974,9 +1980,13 @@ pub async fn create_recipient_disburse_account( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Fetch connector integration details let connector_integration: services::BoxedPayoutConnectorIntegrationInterface< @@ -2077,9 +2087,13 @@ pub async fn cancel_payout( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Fetch connector integration details let connector_integration: services::BoxedPayoutConnectorIntegrationInterface< @@ -2201,9 +2215,13 @@ pub async fn fulfill_payout( payout_data: &mut PayoutData, ) -> RouterResult<()> { // 1. Form Router data - let mut router_data = - core_utils::construct_payout_router_data(connector_data, merchant_account, payout_data) - .await?; + let mut router_data = core_utils::construct_payout_router_data( + state, + connector_data, + merchant_account, + payout_data, + ) + .await?; // 2. Get/Create access token access_token::create_access_token( diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index b07094c321..060fc64f89 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -49,8 +49,8 @@ use crate::{ }; #[allow(clippy::too_many_arguments)] -pub async fn make_payout_method_data<'a>( - state: &'a SessionState, +pub async fn make_payout_method_data( + state: &SessionState, payout_method_data: Option<&api::PayoutMethodData>, payout_token: Option<&str>, customer_id: &id_type::CustomerId, diff --git a/crates/router/src/core/payouts/transformers.rs b/crates/router/src/core/payouts/transformers.rs index c1f3140430..b4e41ecb09 100644 --- a/crates/router/src/core/payouts/transformers.rs +++ b/crates/router/src/core/payouts/transformers.rs @@ -119,6 +119,7 @@ impl } } +#[cfg(feature = "v1")] impl ForeignFrom<( &PayoutRequiredFields, diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 373a268d69..1e788b6ace 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -26,7 +26,7 @@ use crate::{ consts, core::{ errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}, - payments::{self, access_token}, + payments::{self, access_token, helpers}, refunds::transformers::SplitRefundInput, utils as core_utils, }, @@ -116,7 +116,7 @@ pub async fn refund_create_core( req.merchant_connector_details .to_owned() .async_map(|mcd| async { - payments::helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await + helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await }) .await .transpose()?; @@ -237,6 +237,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, }) } errors::ConnectorError::NotSupported { message, connector } => { @@ -249,6 +251,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, }) } _ => None, @@ -284,14 +288,41 @@ pub async fn trigger_refund_to_gateway( }; let refund_update = match router_data_res.response { - Err(err) => storage::RefundUpdate::ErrorUpdate { - refund_status: Some(enums::RefundStatus::Failure), - refund_error_message: err.reason.or(Some(err.message)), - refund_error_code: Some(err.code), - updated_by: storage_scheme.to_string(), - connector_refund_id: None, - connector_refund_data: None, - }, + Err(err) => { + let option_gsm = helpers::get_gsm_record( + state, + Some(err.code.clone()), + Some(err.message.clone()), + connector.connector_name.to_string(), + consts::REFUND_FLOW_STR.to_string(), + ) + .await; + + let gsm_unified_code = option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); + let gsm_unified_message = option_gsm.and_then(|gsm| gsm.unified_message); + + let (unified_code, unified_message) = if let Some((code, message)) = + gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref()) + { + (code.to_owned(), message.to_owned()) + } else { + ( + consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(), + consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), + ) + }; + + storage::RefundUpdate::ErrorUpdate { + refund_status: Some(enums::RefundStatus::Failure), + refund_error_message: err.reason.or(Some(err.message)), + refund_error_code: Some(err.code), + updated_by: storage_scheme.to_string(), + connector_refund_id: None, + connector_refund_data: None, + unified_code: Some(unified_code), + unified_message: Some(unified_message), + } + } Ok(response) => { // match on connector integrity checks match router_data_res.integrity_check.clone() { @@ -319,6 +350,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, connector_refund_data, + unified_code: None, + unified_message: None, } } Ok(()) => { @@ -393,7 +426,7 @@ where // ********************************************** REFUND SYNC ********************************************** -pub async fn refund_response_wrapper<'a, F, Fut, T, Req>( +pub async fn refund_response_wrapper( state: SessionState, merchant_account: domain::MerchantAccount, profile_id: Option, @@ -461,7 +494,7 @@ pub async fn refund_retrieve_core( .merchant_connector_details .to_owned() .async_map(|mcd| async { - payments::helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await + helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await }) .await .transpose()?; @@ -479,6 +512,26 @@ pub async fn refund_retrieve_core( }) .transpose()?; + let unified_translated_message = if let (Some(unified_code), Some(unified_message)) = + (refund.unified_code.clone(), refund.unified_message.clone()) + { + helpers::get_unified_translation( + &state, + unified_code, + unified_message.clone(), + state.locale.to_string(), + ) + .await + .or(Some(unified_message)) + } else { + refund.unified_message + }; + + let refund = storage::Refund { + unified_message: unified_translated_message, + ..refund + }; + let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) { Box::pin(sync_refund_with_gateway( &state, @@ -617,6 +670,8 @@ pub async fn sync_refund_with_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, } } Ok(response) => match router_data_res.integrity_check.clone() { @@ -645,6 +700,8 @@ pub async fn sync_refund_with_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, connector_refund_data, + unified_code: None, + unified_message: None, } } Ok(()) => { @@ -899,6 +956,25 @@ pub async fn validate_and_create_refund( } } }; + let unified_translated_message = if let (Some(unified_code), Some(unified_message)) = + (refund.unified_code.clone(), refund.unified_message.clone()) + { + helpers::get_unified_translation( + state, + unified_code, + unified_message.clone(), + state.locale.to_string(), + ) + .await + .or(Some(unified_message)) + } else { + refund.unified_message + }; + + let refund = storage::Refund { + unified_message: unified_translated_message, + ..refund + }; Ok(refund.foreign_into()) } @@ -1199,6 +1275,8 @@ impl ForeignFrom for api::RefundResponse { connector: refund.connector, merchant_connector_id: refund.merchant_connector_id, split_refunds: refund.split_refunds, + unified_code: refund.unified_code, + unified_message: refund.unified_message, } } } diff --git a/crates/router/src/core/relay.rs b/crates/router/src/core/relay.rs index d25296635a..9492816452 100644 --- a/crates/router/src/core/relay.rs +++ b/crates/router/src/core/relay.rs @@ -109,9 +109,15 @@ pub async fn relay_refund( let merchant_id = merchant_account.get_id(); + #[cfg(feature = "v1")] + let connector_name = &connector_account.connector_name; + + #[cfg(feature = "v2")] + let connector_name = &connector_account.connector_name.to_string(); + let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, - &connector_account.connector_name, + connector_name, api::GetToken::Connector, Some(connector_id.clone()), )?; @@ -124,7 +130,6 @@ pub async fn relay_refund( let router_data = utils::construct_relay_refund_router_data( state, - &connector_account.connector_name, merchant_id, &connector_account, relay_record, @@ -289,9 +294,15 @@ pub async fn sync_relay_refund_with_gateway( let connector_id = &relay_record.connector_id; let merchant_id = merchant_account.get_id(); + #[cfg(feature = "v1")] + let connector_name = &connector_account.connector_name; + + #[cfg(feature = "v2")] + let connector_name = &connector_account.connector_name.to_string(); + let connector_data: api::ConnectorData = api::ConnectorData::get_connector_by_name( &state.conf.connectors, - &connector_account.connector_name, + connector_name, api::GetToken::Connector, Some(connector_id.clone()), ) @@ -300,7 +311,6 @@ pub async fn sync_relay_refund_with_gateway( let router_data = utils::construct_relay_refund_router_data( state, - &connector_account.connector_name, merchant_id, &connector_account, relay_record, diff --git a/crates/router/src/core/relay/utils.rs b/crates/router/src/core/relay/utils.rs index 946f8df7d6..0f28db0f0e 100644 --- a/crates/router/src/core/relay/utils.rs +++ b/crates/router/src/core/relay/utils.rs @@ -17,9 +17,8 @@ const IRRELEVANT_PAYMENT_INTENT_ID: &str = "irrelevant_payment_intent_id"; const IRRELEVANT_PAYMENT_ATTEMPT_ID: &str = "irrelevant_payment_attempt_id"; -pub async fn construct_relay_refund_router_data<'a, F>( - state: &'a SessionState, - connector_name: &str, +pub async fn construct_relay_refund_router_data( + state: &SessionState, merchant_id: &id_type::MerchantId, connector_account: &domain::MerchantConnectorAccount, relay_record: &hyperswitch_domain_models::relay::Relay, @@ -29,10 +28,16 @@ pub async fn construct_relay_refund_router_data<'a, F>( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while parsing value for ConnectorAuthType")?; + #[cfg(feature = "v2")] + let connector_name = &connector_account.connector_name.to_string(); + + #[cfg(feature = "v1")] + let connector_name = &connector_account.connector_name; + let webhook_url = Some(payments::helpers::create_webhook_url( &state.base_url.clone(), merchant_id, - connector_name, + connector_account.get_id().get_string_repr(), )); let supported_connector = &state @@ -71,6 +76,7 @@ pub async fn construct_relay_refund_router_data<'a, F>( flow: std::marker::PhantomData, merchant_id: merchant_id.clone(), customer_id: None, + tenant_id: state.tenant.tenant_id.clone(), connector: connector_name.to_string(), payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(), attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(), @@ -80,7 +86,7 @@ pub async fn construct_relay_refund_router_data<'a, F>( description: None, address: hyperswitch_domain_models::payment_address::PaymentAddress::default(), auth_type: common_enums::AuthenticationType::default(), - connector_meta_data: None, + connector_meta_data: connector_account.metadata.clone(), connector_wallets_details: None, amount_captured: None, payment_method_status: None, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 159def3862..ca14e3e7e4 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -300,10 +300,13 @@ pub struct RoutingAlgorithmHelpers<'h> { #[derive(Clone, Debug)] pub struct ConnectNameAndMCAIdForProfile<'a>( - pub FxHashSet<(&'a String, id_type::MerchantConnectorAccountId)>, + pub FxHashSet<( + &'a common_enums::connector_enums::Connector, + id_type::MerchantConnectorAccountId, + )>, ); #[derive(Clone, Debug)] -pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a String>); +pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a common_enums::connector_enums::Connector>); #[cfg(feature = "v2")] #[derive(Clone, Debug)] @@ -368,23 +371,25 @@ impl RoutingAlgorithmHelpers<'_> { choice: &routing_types::RoutableConnectorChoice, ) -> RouterResult<()> { if let Some(ref mca_id) = choice.merchant_connector_id { + let connector_choice = common_enums::connector_enums::Connector::from(choice.connector); error_stack::ensure!( - self.name_mca_id_set.0.contains(&(&choice.connector.to_string(), mca_id.clone())), - errors::ApiErrorResponse::InvalidRequestData { - message: format!( - "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile", - choice.connector, - mca_id, - ) - } - ); + self.name_mca_id_set.0.contains(&(&connector_choice, mca_id.clone())), + errors::ApiErrorResponse::InvalidRequestData { + message: format!( + "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile", + connector_choice, + mca_id, + ) + } + ); } else { + let connector_choice = common_enums::connector_enums::Connector::from(choice.connector); error_stack::ensure!( - self.name_set.0.contains(&choice.connector.to_string()), + self.name_set.0.contains(&connector_choice), errors::ApiErrorResponse::InvalidRequestData { message: format!( "connector with name '{}' not found for the given profile", - choice.connector, + connector_choice, ) } ); @@ -570,7 +575,7 @@ pub fn get_default_config_key( /// Retrieves cached success_based routing configs specific to tenant and profile #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -pub async fn get_cached_success_based_routing_config_for_profile<'a>( +pub async fn get_cached_success_based_routing_config_for_profile( state: &SessionState, key: &str, ) -> Option> { @@ -709,7 +714,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( ); let success_based_connectors = client - .calculate_success_rate( + .calculate_entity_and_global_success_rate( business_profile.get_id().get_string_repr().into(), success_based_routing_configs.clone(), success_based_routing_config_params.clone(), @@ -725,28 +730,34 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( let payment_status_attribute = get_desired_payment_status_for_success_routing_metrics(payment_attempt.status); - let first_success_based_connector_label = &success_based_connectors - .labels_with_score + let first_merchant_success_based_connector = &success_based_connectors + .entity_scores_with_labels .first() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable( "unable to fetch the first connector from list of connectors obtained from dynamic routing service", - )? - .label - .to_string(); + )?; - let (first_success_based_connector, _) = first_success_based_connector_label + let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label .split_once(':') .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable(format!( "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", - first_success_based_connector_label + first_merchant_success_based_connector.label ))?; + let first_global_success_based_connector = &success_based_connectors + .global_scores_with_labels + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", + )?; + let outcome = get_success_based_metrics_outcome_for_payment( payment_status_attribute, payment_connector.to_string(), - first_success_based_connector.to_string(), + first_merchant_success_based_connector_label.to_string(), ); let dynamic_routing_stats = DynamicRoutingStatsNew { @@ -755,7 +766,8 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( merchant_id: payment_attempt.merchant_id.to_owned(), profile_id: payment_attempt.profile_id.to_owned(), amount: payment_attempt.get_total_amount(), - success_based_routing_connector: first_success_based_connector.to_string(), + success_based_routing_connector: first_merchant_success_based_connector_label + .to_string(), payment_connector: payment_connector.to_string(), payment_method_type: payment_attempt.payment_method_type, currency: payment_attempt.currency, @@ -765,6 +777,9 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( payment_status: payment_attempt.status, conclusive_classification: outcome, created_at: common_utils::date_time::now(), + global_success_based_connector: Some( + first_global_success_based_connector.label.to_string(), + ), }; core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( @@ -783,8 +798,20 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( ), ), ( - "success_based_routing_connector", - first_success_based_connector.to_string(), + "merchant_specific_success_based_routing_connector", + first_merchant_success_based_connector_label.to_string(), + ), + ( + "merchant_specific_success_based_routing_connector_score", + first_merchant_success_based_connector.score.to_string(), + ), + ( + "global_success_based_routing_connector", + first_global_success_based_connector.label.to_string(), + ), + ( + "global_success_based_routing_connector_score", + first_global_success_based_connector.score.to_string(), ), ("payment_connector", payment_connector.to_string()), ( diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index 5ed1b63d72..6e47dcac03 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -12,7 +12,7 @@ use hyperswitch_domain_models::{ }, }; -use super::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType}; +use super::errors::RouterResult; use crate::{ core::{ errors::utils::StorageErrorExt, @@ -23,6 +23,7 @@ use crate::{ }, db::domain, routes::SessionState, + types::domain::MerchantConnectorAccount, }; #[cfg(feature = "v1")] @@ -33,7 +34,7 @@ impl UnifiedAuthenticationService for ClickToPay { _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, payment_data: &PaymentData, - merchant_connector_account: &MerchantConnectorAccountType, + merchant_connector_account: &MerchantConnectorAccount, connector_name: &str, authentication_id: &str, payment_method: common_enums::PaymentMethod, @@ -43,6 +44,7 @@ impl UnifiedAuthenticationService for ClickToPay { let pre_auth_router_data: hyperswitch_domain_models::types::UasPreAuthenticationRouterData = utils::construct_uas_router_data( + state, connector_name.to_string(), payment_method, payment_data.payment_attempt.merchant_id.clone(), @@ -67,7 +69,7 @@ impl UnifiedAuthenticationService for ClickToPay { _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, payment_data: &PaymentData, - merchant_connector_account: &MerchantConnectorAccountType, + merchant_connector_account: &MerchantConnectorAccount, connector_name: &str, payment_method: common_enums::PaymentMethod, ) -> RouterResult { @@ -81,6 +83,7 @@ impl UnifiedAuthenticationService for ClickToPay { let post_authentication_data = UasPostAuthenticationRequestData {}; let post_auth_router_data: hyperswitch_domain_models::types::UasPostAuthenticationRouterData = utils::construct_uas_router_data( + state, connector_name.to_string(), payment_method, payment_data.payment_attempt.merchant_id.clone(), @@ -104,7 +107,7 @@ impl UnifiedAuthenticationService for ClickToPay { _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, + _merchant_connector_account: &MerchantConnectorAccount, ) -> RouterResult<()> { Ok(()) } diff --git a/crates/router/src/core/unified_authentication_service/types.rs b/crates/router/src/core/unified_authentication_service/types.rs index db0251768c..a400f62ffc 100644 --- a/crates/router/src/core/unified_authentication_service/types.rs +++ b/crates/router/src/core/unified_authentication_service/types.rs @@ -1,10 +1,8 @@ use crate::{ - core::{ - errors::RouterResult, - payments::{helpers::MerchantConnectorAccountType, PaymentData}, - }, + core::{errors::RouterResult, payments::PaymentData}, db::domain, routes::SessionState, + types::domain::MerchantConnectorAccount, }; pub const CTP_MASTERCARD: &str = "ctp_mastercard"; @@ -27,7 +25,7 @@ pub trait UnifiedAuthenticationService { _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, _payment_data: &PaymentData, - _merchant_connector_account: &MerchantConnectorAccountType, + _merchant_connector_account: &MerchantConnectorAccount, _connector_name: &str, _authentication_id: &str, _payment_method: common_enums::PaymentMethod, @@ -38,7 +36,7 @@ pub trait UnifiedAuthenticationService { _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, _payment_data: &PaymentData, - _merchant_connector_account: &MerchantConnectorAccountType, + _merchant_connector_account: &MerchantConnectorAccount, _connector_name: &str, _payment_method: common_enums::PaymentMethod, ) -> RouterResult; @@ -47,6 +45,6 @@ pub trait UnifiedAuthenticationService { _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, + _merchant_connector_account: &MerchantConnectorAccount, ) -> RouterResult<()>; } diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index fe4f864604..c74baa3a0b 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -1,7 +1,6 @@ use std::marker::PhantomData; use common_enums::enums::PaymentMethod; -use common_utils::ext_traits::ValueExt; use diesel_models::authentication::{Authentication, AuthenticationUpdate}; use error_stack::ResultExt; use hyperswitch_domain_models::{ @@ -21,10 +20,9 @@ use crate::{ core::{ errors::{utils::ConnectorErrorExt, RouterResult}, payments, - unified_authentication_service::MerchantConnectorAccountType, }, services::{self, execute_connector_processing_step}, - types::api, + types::{api, domain::MerchantConnectorAccount}, SessionState, }; @@ -102,19 +100,19 @@ where Ok(router_data) } +#[allow(clippy::too_many_arguments)] pub fn construct_uas_router_data( + state: &SessionState, authentication_connector_name: String, payment_method: PaymentMethod, merchant_id: common_utils::id_type::MerchantId, address: Option, request_data: Req, - merchant_connector_account: &MerchantConnectorAccountType, + merchant_connector_account: &MerchantConnectorAccount, authentication_id: Option, ) -> RouterResult> { - let test_mode: Option = merchant_connector_account.is_test_mode_on(); let auth_type: ConnectorAuthType = merchant_connector_account .get_connector_account_details() - .parse_value("ConnectorAuthType") .change_context(ApiErrorResponse::InternalServerError)?; Ok(RouterData { flow: PhantomData, @@ -125,6 +123,7 @@ pub fn construct_uas_router_data( payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("authentication") .get_string_repr() .to_owned(), + tenant_id: state.tenant.tenant_id.clone(), attempt_id: IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW.to_owned(), status: common_enums::AttemptStatus::default(), payment_method, @@ -132,7 +131,7 @@ pub fn construct_uas_router_data( description: None, address: address.unwrap_or_default(), auth_type: common_enums::AuthenticationType::default(), - connector_meta_data: merchant_connector_account.get_metadata(), + connector_meta_data: merchant_connector_account.metadata.clone(), connector_wallets_details: merchant_connector_account.get_connector_wallets_details(), amount_captured: None, minor_amount_captured: None, @@ -152,7 +151,7 @@ pub fn construct_uas_router_data( payout_method_data: None, #[cfg(feature = "payouts")] quote_id: None, - test_mode, + test_mode: None, connector_http_status_code: None, external_latency: None, apple_pay_flow: None, diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index a7c60f33ff..52d7d6a252 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -116,10 +116,14 @@ pub async fn get_user_details( ) -> UserResponse { let user = user_from_token.get_user_from_db(&state).await?; let verification_days_left = utils::user::get_verification_days_left(&state, &user)?; - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -633,6 +637,10 @@ async fn handle_invitation( &request.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .to_not_found_response(UserErrors::InvalidRoleId)?; @@ -1155,10 +1163,14 @@ pub async fn resend_invite( .get_entity_id_and_type() .ok_or(UserErrors::InternalServerError)?; - let invitee_role_info = roles::RoleInfo::from_role_id_and_org_id( + let invitee_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_role.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -1401,7 +1413,7 @@ pub async fn create_tenant_user( .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? .pop() - .ok_or(UserErrors::InternalServerError) + .ok_or(UserErrors::InvalidRoleOperation) .attach_printable("No merchants found in the tenancy")?; let new_user = domain::NewUser::try_from(( @@ -1490,10 +1502,14 @@ pub async fn list_user_roles_details( .await .to_not_found_response(UserErrors::InvalidRoleOperation)?; - let requestor_role_info = roles::RoleInfo::from_role_id_and_org_id( + let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .to_not_found_response(UserErrors::InternalServerError) @@ -1644,10 +1660,14 @@ pub async fn list_user_roles_details( .collect::>() .into_iter() .map(|role_id| async { - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -2757,10 +2777,14 @@ pub async fn list_orgs_for_user( state: SessionState, user_from_token: auth::UserFromToken, ) -> UserResponse> { - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -2834,10 +2858,14 @@ pub async fn list_merchants_for_user_in_org( state: SessionState, user_from_token: auth::UserFromToken, ) -> UserResponse> { - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -2909,10 +2937,14 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account( state: SessionState, user_from_token: auth::UserFromToken, ) -> UserResponse> { - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -3001,10 +3033,14 @@ pub async fn switch_org_for_user( .into()); } - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError) @@ -3092,12 +3128,20 @@ pub async fn switch_org_for_user( request.org_id.clone(), role_id.clone(), profile_id.clone(), - user_from_token.tenant_id, + user_from_token.tenant_id.clone(), ) .await?; - utils::user_role::set_role_info_in_cache_by_role_id_org_id(&state, &role_id, &request.org_id) - .await; + utils::user_role::set_role_info_in_cache_by_role_id_org_id( + &state, + &role_id, + &request.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + ) + .await; let response = user_api::TokenResponse { token: token.clone(), @@ -3120,10 +3164,14 @@ pub async fn switch_merchant_for_user_in_org( } let key_manager_state = &(&state).into(); - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError) @@ -3275,11 +3323,20 @@ pub async fn switch_merchant_for_user_in_org( org_id.clone(), role_id.clone(), profile_id, - user_from_token.tenant_id, + user_from_token.tenant_id.clone(), ) .await?; - utils::user_role::set_role_info_in_cache_by_role_id_org_id(&state, &role_id, &org_id).await; + utils::user_role::set_role_info_in_cache_by_role_id_org_id( + &state, + &role_id, + &org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + ) + .await; let response = user_api::TokenResponse { token: token.clone(), @@ -3302,10 +3359,14 @@ pub async fn switch_profile_for_user_in_org_and_merchant( } let key_manager_state = &(&state).into(); - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError) @@ -3378,7 +3439,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant( user_from_token.org_id.clone(), role_id.clone(), profile_id, - user_from_token.tenant_id, + user_from_token.tenant_id.clone(), ) .await?; @@ -3386,6 +3447,10 @@ pub async fn switch_profile_for_user_in_org_and_merchant( &state, &role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await; diff --git a/crates/router/src/core/user/dashboard_metadata.rs b/crates/router/src/core/user/dashboard_metadata.rs index aa67a70223..689762c1f4 100644 --- a/crates/router/src/core/user/dashboard_metadata.rs +++ b/crates/router/src/core/user/dashboard_metadata.rs @@ -34,6 +34,7 @@ pub async fn set_metadata( Ok(ApplicationResponse::StatusOk) } +#[cfg(feature = "v1")] pub async fn get_multiple_metadata( state: SessionState, user: UserFromToken, @@ -622,6 +623,7 @@ async fn fetch_metadata( Ok(dashboard_metadata) } +#[cfg(feature = "v1")] pub async fn backfill_metadata( state: &SessionState, user: &UserFromToken, diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index d8fdff0e62..19d91b14f0 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -83,10 +83,14 @@ pub async fn get_parent_group_info( state: SessionState, user_from_token: auth::UserFromToken, ) -> UserResponse> { - let role_info = roles::RoleInfo::from_role_id_and_org_id( + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .to_not_found_response(UserErrors::InvalidRoleId)?; @@ -123,6 +127,10 @@ pub async fn update_user_role( &req.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .to_not_found_response(UserErrors::InvalidRoleId)?; @@ -143,10 +151,14 @@ pub async fn update_user_role( .attach_printable("User Changing their own role"); } - let updator_role = roles::RoleInfo::from_role_id_and_org_id( + let updator_role = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -179,10 +191,14 @@ pub async fn update_user_role( }; if let Some(user_role) = v2_user_role_to_be_updated { - let role_to_be_updated = roles::RoleInfo::from_role_id_and_org_id( + let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_role.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -259,10 +275,14 @@ pub async fn update_user_role( }; if let Some(user_role) = v1_user_role_to_be_updated { - let role_to_be_updated = roles::RoleInfo::from_role_id_and_org_id( + let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_role.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -485,10 +505,14 @@ pub async fn delete_user_role( .attach_printable("User deleting himself"); } - let deletion_requestor_role_info = roles::RoleInfo::from_role_id_and_org_id( + let deletion_requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -527,6 +551,10 @@ pub async fn delete_user_role( &role_to_be_deleted.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -597,6 +625,10 @@ pub async fn delete_user_role( &role_to_be_deleted.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -680,10 +712,14 @@ pub async fn list_users_in_lineage( user_from_token: auth::UserFromToken, request: user_role_api::ListUsersInEntityRequest, ) -> UserResponse> { - let requestor_role_info = roles::RoleInfo::from_role_id_and_org_id( + let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_from_token.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .change_context(UserErrors::InternalServerError)?; @@ -692,7 +728,49 @@ pub async fn list_users_in_lineage( requestor_role_info.get_entity_type(), request.entity_type, )? { - EntityType::Tenant | EntityType::Organization => { + EntityType::Tenant => { + let mut org_users = utils::user_role::fetch_user_roles_by_payload( + &state, + ListUserRolesByOrgIdPayload { + user_id: None, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + org_id: &user_from_token.org_id, + merchant_id: None, + profile_id: None, + version: None, + limit: None, + }, + request.entity_type, + ) + .await?; + + // Fetch tenant user + let tenant_user = state + .global_store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id: &user_from_token.user_id, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + org_id: None, + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: None, + limit: None, + }) + .await + .change_context(UserErrors::InternalServerError)?; + + org_users.extend(tenant_user); + org_users + } + EntityType::Organization => { utils::user_role::fetch_user_roles_by_payload( &state, ListUserRolesByOrgIdPayload { @@ -777,10 +855,14 @@ pub async fn list_users_in_lineage( let role_info_map = futures::future::try_join_all(user_roles_set.iter().map(|user_role| async { - roles::RoleInfo::from_role_id_and_org_id( + roles::RoleInfo::from_role_id_org_id_tenant_id( &state, &user_role.role_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .map(|role_info| { diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index e897e1b336..ca4c062244 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -73,16 +73,21 @@ pub async fn create_role( &role_name, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await?; let user_role_info = user_from_token.get_role_info_from_db(&state).await?; if matches!(req.role_scope, RoleScope::Organization) - && user_role_info.get_entity_type() != EntityType::Organization + && user_role_info.get_entity_type() < EntityType::Organization { - return Err(report!(UserErrors::InvalidRoleOperation)) - .attach_printable("Non org admin user creating org level role"); + return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable( + "User does not have sufficient privileges to perform organization-level role operation", + ); } let role = state @@ -99,6 +104,7 @@ pub async fn create_role( last_modified_by: user_from_token.user_id, created_at: now, last_modified_at: now, + tenant_id: user_from_token.tenant_id.unwrap_or(state.tenant.tenant_id), }) .await .to_duplicate_response(UserErrors::RoleNameAlreadyExists)?; @@ -118,10 +124,17 @@ pub async fn get_role_with_groups( user_from_token: UserFromToken, role: role_api::GetRoleRequest, ) -> UserResponse { - let role_info = - roles::RoleInfo::from_role_id_and_org_id(&state, &role.role_id, &user_from_token.org_id) - .await - .to_not_found_response(UserErrors::InvalidRoleId)?; + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( + &state, + &role.role_id, + &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + ) + .await + .to_not_found_response(UserErrors::InvalidRoleId)?; if role_info.is_internal() { return Err(UserErrors::InvalidRoleId.into()); @@ -142,10 +155,17 @@ pub async fn get_parent_info_for_role( user_from_token: UserFromToken, role: role_api::GetRoleRequest, ) -> UserResponse { - let role_info = - roles::RoleInfo::from_role_id_and_org_id(&state, &role.role_id, &user_from_token.org_id) - .await - .to_not_found_response(UserErrors::InvalidRoleId)?; + let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id( + &state, + &role.role_id, + &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + ) + .await + .to_not_found_response(UserErrors::InvalidRoleId)?; if role_info.is_internal() { return Err(UserErrors::InvalidRoleId.into()); @@ -193,6 +213,10 @@ pub async fn update_role( role_name, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await?; } @@ -206,6 +230,10 @@ pub async fn update_role( role_id, &user_from_token.merchant_id, &user_from_token.org_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), ) .await .to_not_found_response(UserErrors::InvalidRoleOperation)?; @@ -273,6 +301,10 @@ pub async fn list_roles_with_info( EntityType::Tenant | EntityType::Organization => state .global_store .list_roles_for_org_by_parameters( + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, None, request.entity_type, @@ -284,6 +316,10 @@ pub async fn list_roles_with_info( EntityType::Merchant => state .global_store .list_roles_for_org_by_parameters( + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, Some(&user_from_token.merchant_id), request.entity_type, @@ -346,6 +382,10 @@ pub async fn list_roles_at_entity_level( EntityType::Tenant | EntityType::Organization => state .global_store .list_roles_for_org_by_parameters( + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, None, Some(req.entity_type), @@ -358,6 +398,10 @@ pub async fn list_roles_at_entity_level( EntityType::Merchant => state .global_store .list_roles_for_org_by_parameters( + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, Some(&user_from_token.merchant_id), Some(req.entity_type), diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index c441e32581..0f775eda85 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -54,6 +54,7 @@ const IRRELEVANT_ATTEMPT_ID_IN_DISPUTE_FLOW: &str = "irrelevant_attempt_id_in_di #[cfg(all(feature = "payouts", feature = "v2", feature = "customer_v2"))] #[instrument(skip_all)] pub async fn construct_payout_router_data<'a, F>( + _state: &SessionState, _connector_data: &api::ConnectorData, _merchant_account: &domain::MerchantAccount, _payout_data: &mut PayoutData, @@ -68,6 +69,7 @@ pub async fn construct_payout_router_data<'a, F>( ))] #[instrument(skip_all)] pub async fn construct_payout_router_data<'a, F>( + state: &SessionState, connector_data: &api::ConnectorData, merchant_account: &domain::MerchantAccount, payout_data: &mut PayoutData, @@ -152,6 +154,7 @@ pub async fn construct_payout_router_data<'a, F>( flow: PhantomData, merchant_id: merchant_account.get_id().to_owned(), customer_id: customer_details.to_owned().map(|c| c.customer_id), + tenant_id: state.tenant.tenant_id.clone(), connector_customer: connector_customer_id, connector: connector_name.to_string(), payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("payout") @@ -285,11 +288,16 @@ pub async fn construct_refund_router_data<'a, F>( .payment_method .get_required_value("payment_method_type") .change_context(errors::ApiErrorResponse::InternalServerError)?; + let merchant_connector_account_id_or_connector_name = payment_attempt + .merchant_connector_id + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_id); let webhook_url = Some(helpers::create_webhook_url( &state.base_url.clone(), merchant_account.get_id(), - connector_id, + merchant_connector_account_id_or_connector_name, )); let test_mode: Option = merchant_connector_account.is_test_mode_on(); @@ -330,6 +338,7 @@ pub async fn construct_refund_router_data<'a, F>( flow: PhantomData, merchant_id: merchant_account.get_id().clone(), customer_id: payment_intent.customer_id.to_owned(), + tenant_id: state.tenant.tenant_id.clone(), connector: connector_id.to_string(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), attempt_id: payment_attempt.attempt_id.clone(), @@ -652,6 +661,7 @@ pub async fn construct_accept_dispute_router_data<'a>( flow: PhantomData, merchant_id: merchant_account.get_id().clone(), connector: dispute.connector.to_string(), + tenant_id: state.tenant.tenant_id.clone(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, @@ -753,6 +763,7 @@ pub async fn construct_submit_evidence_router_data<'a>( merchant_id: merchant_account.get_id().clone(), connector: connector_id.to_string(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), + tenant_id: state.tenant.tenant_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, payment_method, @@ -851,6 +862,7 @@ pub async fn construct_upload_file_router_data<'a>( merchant_id: merchant_account.get_id().clone(), connector: connector_id.to_string(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), + tenant_id: state.tenant.tenant_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, payment_method, @@ -910,8 +922,8 @@ pub async fn construct_upload_file_router_data<'a>( } #[cfg(feature = "v2")] -pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone>( - state: &'a SessionState, +pub async fn construct_payments_dynamic_tax_calculation_router_data( + state: &SessionState, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, @@ -921,8 +933,8 @@ pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone } #[cfg(feature = "v1")] -pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone>( - state: &'a SessionState, +pub async fn construct_payments_dynamic_tax_calculation_router_data( + state: &SessionState, merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, @@ -978,6 +990,7 @@ pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone connector: merchant_connector_account.connector_name.clone(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), attempt_id: payment_attempt.attempt_id.clone(), + tenant_id: state.tenant.tenant_id.clone(), status: payment_attempt.status, payment_method: diesel_models::enums::PaymentMethod::default(), connector_auth_type, @@ -1076,6 +1089,7 @@ pub async fn construct_defend_dispute_router_data<'a>( merchant_id: merchant_account.get_id().clone(), connector: connector_id.to_string(), payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), + tenant_id: state.tenant.tenant_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, payment_method, @@ -1169,6 +1183,7 @@ pub async fn construct_retrieve_file_router_data<'a>( flow: PhantomData, merchant_id: merchant_account.get_id().clone(), connector: connector_id.to_string(), + tenant_id: state.tenant.tenant_id.clone(), customer_id: None, connector_customer: None, payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("dispute") diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index ff9849958b..4c1a5aeb78 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -22,9 +22,9 @@ use crate::{ core::{ api_locking, errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt}, - metrics, payments, - payments::tokenization, - refunds, utils as core_utils, + metrics, + payments::{self, tokenization}, + refunds, relay, utils as core_utils, webhooks::utils::construct_webhook_router_data, }, db::StorageInterface, @@ -62,6 +62,7 @@ pub async fn incoming_webhooks_wrapper( key_store: domain::MerchantKeyStore, connector_name_or_mca_id: &str, body: actix_web::web::Bytes, + is_relay_webhook: bool, ) -> RouterResponse { let start_instant = Instant::now(); let (application_response, webhooks_response_tracker, serialized_req) = @@ -73,6 +74,7 @@ pub async fn incoming_webhooks_wrapper( key_store, connector_name_or_mca_id, body.clone(), + is_relay_webhook, )) .await?; @@ -118,6 +120,7 @@ pub async fn incoming_webhooks_wrapper( Ok(application_response) } +#[allow(clippy::too_many_arguments)] #[instrument(skip_all)] async fn incoming_webhooks_core( state: SessionState, @@ -127,6 +130,7 @@ async fn incoming_webhooks_core( key_store: domain::MerchantKeyStore, connector_name_or_mca_id: &str, body: actix_web::web::Bytes, + is_relay_webhook: bool, ) -> errors::RouterResult<( services::ApplicationResponse, WebhookResponseTracker, @@ -361,120 +365,162 @@ async fn incoming_webhooks_core( id: profile_id.get_string_repr().to_owned(), })?; - let result_response = match flow_type { - api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow( + // If the incoming webhook is a relay webhook, then we need to trigger the relay webhook flow + let result_response = if is_relay_webhook { + let relay_webhook_response = Box::pin(relay_incoming_webhook_flow( state.clone(), - req_state, merchant_account, business_profile, key_store, webhook_details, - source_verified, - &connector, - &request_details, event_type, - )) - .await - .attach_printable("Incoming webhook flow for payments failed"), - - api::WebhookFlow::Refund => Box::pin(refunds_incoming_webhook_flow( - state.clone(), - merchant_account, - business_profile, - key_store, - webhook_details, - connector_name.as_str(), source_verified, - event_type, )) .await - .attach_printable("Incoming webhook flow for refunds failed"), + .attach_printable("Incoming webhook flow for relay failed"); - api::WebhookFlow::Dispute => Box::pin(disputes_incoming_webhook_flow( - state.clone(), - merchant_account, - business_profile, - key_store, - webhook_details, - source_verified, - &connector, - &request_details, - event_type, - )) - .await - .attach_printable("Incoming webhook flow for disputes failed"), + // Using early return ensures unsupported webhooks are acknowledged to the connector + if let Some(errors::ApiErrorResponse::NotSupported { .. }) = relay_webhook_response + .as_ref() + .err() + .map(|a| a.current_context()) + { + logger::error!( + webhook_payload =? request_details.body, + "Failed while identifying the event type", + ); - api::WebhookFlow::BankTransfer => Box::pin(bank_transfer_webhook_flow( - state.clone(), - req_state, - merchant_account, - business_profile, - key_store, - webhook_details, - source_verified, - )) - .await - .attach_printable("Incoming bank-transfer webhook flow failed"), + let response = connector + .get_webhook_api_response(&request_details, None) + .switch() + .attach_printable( + "Failed while early return in case of not supported event type in relay webhooks", + )?; + + return Ok(( + response, + WebhookResponseTracker::NoEffect, + serde_json::Value::Null, + )); + }; - api::WebhookFlow::ReturnResponse => Ok(WebhookResponseTracker::NoEffect), + relay_webhook_response + } else { + match flow_type { + api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow( + state.clone(), + req_state, + merchant_account, + business_profile, + key_store, + webhook_details, + source_verified, + &connector, + &request_details, + event_type, + )) + .await + .attach_printable("Incoming webhook flow for payments failed"), - api::WebhookFlow::Mandate => Box::pin(mandates_incoming_webhook_flow( - state.clone(), - merchant_account, - business_profile, - key_store, - webhook_details, - source_verified, - event_type, - )) - .await - .attach_printable("Incoming webhook flow for mandates failed"), + api::WebhookFlow::Refund => Box::pin(refunds_incoming_webhook_flow( + state.clone(), + merchant_account, + business_profile, + key_store, + webhook_details, + connector_name.as_str(), + source_verified, + event_type, + )) + .await + .attach_printable("Incoming webhook flow for refunds failed"), + + api::WebhookFlow::Dispute => Box::pin(disputes_incoming_webhook_flow( + state.clone(), + merchant_account, + business_profile, + key_store, + webhook_details, + source_verified, + &connector, + &request_details, + event_type, + )) + .await + .attach_printable("Incoming webhook flow for disputes failed"), - api::WebhookFlow::ExternalAuthentication => { - Box::pin(external_authentication_incoming_webhook_flow( + api::WebhookFlow::BankTransfer => Box::pin(bank_transfer_webhook_flow( + state.clone(), + req_state, + merchant_account, + business_profile, + key_store, + webhook_details, + source_verified, + )) + .await + .attach_printable("Incoming bank-transfer webhook flow failed"), + + api::WebhookFlow::ReturnResponse => Ok(WebhookResponseTracker::NoEffect), + + api::WebhookFlow::Mandate => Box::pin(mandates_incoming_webhook_flow( + state.clone(), + merchant_account, + business_profile, + key_store, + webhook_details, + source_verified, + event_type, + )) + .await + .attach_printable("Incoming webhook flow for mandates failed"), + + api::WebhookFlow::ExternalAuthentication => { + Box::pin(external_authentication_incoming_webhook_flow( + state.clone(), + req_state, + merchant_account, + key_store, + source_verified, + event_type, + &request_details, + &connector, + object_ref_id, + business_profile, + merchant_connector_account, + )) + .await + .attach_printable("Incoming webhook flow for external authentication failed") + } + api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( state.clone(), req_state, merchant_account, key_store, source_verified, event_type, - &request_details, - &connector, object_ref_id, business_profile, - merchant_connector_account, )) .await - .attach_printable("Incoming webhook flow for external authentication failed") - } - api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( - state.clone(), - req_state, - merchant_account, - key_store, - source_verified, - event_type, - object_ref_id, - business_profile, - )) - .await - .attach_printable("Incoming webhook flow for fraud check failed"), + .attach_printable("Incoming webhook flow for fraud check failed"), - #[cfg(feature = "payouts")] - api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( - state.clone(), - merchant_account, - business_profile, - key_store, - webhook_details, - event_type, - source_verified, - )) - .await - .attach_printable("Incoming webhook flow for payouts failed"), + #[cfg(feature = "payouts")] + api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( + state.clone(), + merchant_account, + business_profile, + key_store, + webhook_details, + event_type, + source_verified, + )) + .await + .attach_printable("Incoming webhook flow for payouts failed"), - _ => Err(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unsupported Flow Type received in incoming webhooks"), + _ => Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unsupported Flow Type received in incoming webhooks"), + } }; match result_response { @@ -836,6 +882,97 @@ async fn payouts_incoming_webhook_flow( } } +async fn relay_refunds_incoming_webhook_flow( + state: SessionState, + merchant_account: domain::MerchantAccount, + business_profile: domain::Profile, + merchant_key_store: domain::MerchantKeyStore, + webhook_details: api::IncomingWebhookDetails, + event_type: webhooks::IncomingWebhookEvent, + source_verified: bool, +) -> CustomResult { + let db = &*state.store; + let key_manager_state = &(&state).into(); + + let relay_record = match webhook_details.object_reference_id { + webhooks::ObjectReferenceId::RefundId(refund_id_type) => match refund_id_type { + webhooks::RefundIdType::RefundId(refund_id) => { + let relay_id = common_utils::id_type::RelayId::from_str(&refund_id) + .change_context(errors::ValidationError::IncorrectValueProvided { + field_name: "relay_id", + }) + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + db.find_relay_by_id(key_manager_state, &merchant_key_store, &relay_id) + .await + .to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound) + .attach_printable("Failed to fetch the relay record")? + } + webhooks::RefundIdType::ConnectorRefundId(connector_refund_id) => db + .find_relay_by_profile_id_connector_reference_id( + key_manager_state, + &merchant_key_store, + business_profile.get_id(), + &connector_refund_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound) + .attach_printable("Failed to fetch the relay record")?, + }, + _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("received a non-refund id when processing relay refund webhooks")?, + }; + + // if source_verified then update relay status else trigger relay force sync + let relay_response = if source_verified { + let relay_update = hyperswitch_domain_models::relay::RelayUpdate::StatusUpdate { + connector_reference_id: None, + status: common_enums::RelayStatus::foreign_try_from(event_type) + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("failed relay refund status mapping from event type")?, + }; + db.update_relay( + key_manager_state, + &merchant_key_store, + relay_record, + relay_update, + ) + .await + .map(api_models::relay::RelayResponse::from) + .to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound) + .attach_printable("Failed to update relay")? + } else { + let relay_retrieve_request = api_models::relay::RelayRetrieveRequest { + force_sync: true, + id: relay_record.id, + }; + let relay_force_sync_response = Box::pin(relay::relay_retrieve( + state, + merchant_account, + Some(business_profile.get_id().clone()), + merchant_key_store, + relay_retrieve_request, + )) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to force sync relay")?; + + if let hyperswitch_domain_models::api::ApplicationResponse::Json(response) = + relay_force_sync_response + { + response + } else { + Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unexpected response from force sync relay")? + } + }; + + Ok(WebhookResponseTracker::Relay { + relay_id: relay_response.id, + status: relay_response.status, + }) +} + #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] async fn refunds_incoming_webhook_flow( @@ -938,6 +1075,44 @@ async fn refunds_incoming_webhook_flow( }) } +async fn relay_incoming_webhook_flow( + state: SessionState, + merchant_account: domain::MerchantAccount, + business_profile: domain::Profile, + merchant_key_store: domain::MerchantKeyStore, + webhook_details: api::IncomingWebhookDetails, + event_type: webhooks::IncomingWebhookEvent, + source_verified: bool, +) -> CustomResult { + let flow_type: api::WebhookFlow = event_type.into(); + + let result_response = match flow_type { + webhooks::WebhookFlow::Refund => Box::pin(relay_refunds_incoming_webhook_flow( + state, + merchant_account, + business_profile, + merchant_key_store, + webhook_details, + event_type, + source_verified, + )) + .await + .attach_printable("Incoming webhook flow for relay refund failed")?, + webhooks::WebhookFlow::Payment + | webhooks::WebhookFlow::Payout + | webhooks::WebhookFlow::Dispute + | webhooks::WebhookFlow::Subscription + | webhooks::WebhookFlow::ReturnResponse + | webhooks::WebhookFlow::BankTransfer + | webhooks::WebhookFlow::Mandate + | webhooks::WebhookFlow::ExternalAuthentication + | webhooks::WebhookFlow::FraudCheck => Err(errors::ApiErrorResponse::NotSupported { + message: "Relay webhook flow types not supported".to_string(), + })?, + }; + Ok(result_response) +} + async fn get_payment_attempt_from_object_reference_id( state: &SessionState, object_reference_id: webhooks::ObjectReferenceId, @@ -1658,6 +1833,7 @@ async fn verify_webhook_source_verification_call( .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; let router_data = construct_webhook_router_data( + state, connector_name, merchant_connector_account, merchant_account, diff --git a/crates/router/src/core/webhooks/incoming_v2.rs b/crates/router/src/core/webhooks/incoming_v2.rs index 569cd330a0..6b01615778 100644 --- a/crates/router/src/core/webhooks/incoming_v2.rs +++ b/crates/router/src/core/webhooks/incoming_v2.rs @@ -56,6 +56,7 @@ pub async fn incoming_webhooks_wrapper( key_store: domain::MerchantKeyStore, connector_id: &common_utils::id_type::MerchantConnectorAccountId, body: actix_web::web::Bytes, + is_relay_webhook: bool, ) -> RouterResponse { let start_instant = Instant::now(); let (application_response, webhooks_response_tracker, serialized_req) = @@ -68,6 +69,7 @@ pub async fn incoming_webhooks_wrapper( key_store, connector_id, body.clone(), + is_relay_webhook, )) .await?; @@ -124,6 +126,7 @@ async fn incoming_webhooks_core( key_store: domain::MerchantKeyStore, connector_id: &common_utils::id_type::MerchantConnectorAccountId, body: actix_web::web::Bytes, + _is_relay_webhook: bool, ) -> errors::RouterResult<( services::ApplicationResponse, WebhookResponseTracker, @@ -665,6 +668,7 @@ async fn verify_webhook_source_verification_call( .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; let router_data = construct_webhook_router_data( + state, connector_name, merchant_connector_account, merchant_account, @@ -755,8 +759,11 @@ async fn fetch_mca_and_connector( }) .attach_printable("error while fetching merchant_connector_account from connector_id")?; - let (connector, connector_name) = - get_connector_by_connector_name(state, &mca.connector_name, Some(mca.get_id()))?; + let (connector, connector_name) = get_connector_by_connector_name( + state, + &mca.connector_name.to_string(), + Some(mca.get_id()), + )?; Ok((mca, connector, connector_name)) } diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index b21ec0056c..5695704286 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -11,6 +11,7 @@ use crate::{ db::{get_and_deserialize_key, StorageInterface}, services::logger, types::{self, api, domain, PaymentAddress}, + SessionState, }; const IRRELEVANT_ATTEMPT_ID_IN_SOURCE_VERIFICATION_FLOW: &str = @@ -56,7 +57,8 @@ pub async fn is_webhook_event_disabled( } } -pub async fn construct_webhook_router_data<'a>( +pub async fn construct_webhook_router_data( + state: &SessionState, connector_name: &str, merchant_connector_account: domain::MerchantConnectorAccount, merchant_account: &domain::MerchantAccount, @@ -74,6 +76,7 @@ pub async fn construct_webhook_router_data<'a>( merchant_id: merchant_account.get_id().clone(), connector: connector_name.to_string(), customer_id: None, + tenant_id: state.tenant.tenant_id.clone(), payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("source_verification_flow") .get_string_repr() .to_owned(), diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index eef84f6ff9..d852319e38 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -6,6 +6,7 @@ pub mod blocklist; pub mod blocklist_fingerprint; pub mod blocklist_lookup; pub mod business_profile; +pub mod callback_mapper; pub mod capture; pub mod cards_info; pub mod configs; diff --git a/crates/router/src/db/callback_mapper.rs b/crates/router/src/db/callback_mapper.rs new file mode 100644 index 0000000000..0697f41bda --- /dev/null +++ b/crates/router/src/db/callback_mapper.rs @@ -0,0 +1,53 @@ +use error_stack::report; +use hyperswitch_domain_models::callback_mapper as domain; +use router_env::{instrument, tracing}; +use storage_impl::DataModelExt; + +use super::Store; +use crate::{ + connection, + core::errors::{self, CustomResult}, + types::storage, +}; + +#[async_trait::async_trait] +pub trait CallbackMapperInterface { + async fn insert_call_back_mapper( + &self, + call_back_mapper: domain::CallbackMapper, + ) -> CustomResult; + + async fn find_call_back_mapper_by_id( + &self, + id: &str, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl CallbackMapperInterface for Store { + #[instrument(skip_all)] + async fn insert_call_back_mapper( + &self, + call_back_mapper: domain::CallbackMapper, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + call_back_mapper + .to_storage_model() + .insert(&conn) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + .map(domain::CallbackMapper::from_storage_model) + } + + #[instrument(skip_all)] + async fn find_call_back_mapper_by_id( + &self, + id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + storage::CallbackMapper::find_by_id(&conn, id) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + .map(domain::CallbackMapper::from_storage_model) + } +} diff --git a/crates/router/src/db/dashboard_metadata.rs b/crates/router/src/db/dashboard_metadata.rs index 742c03dde4..4f1624f345 100644 --- a/crates/router/src/db/dashboard_metadata.rs +++ b/crates/router/src/db/dashboard_metadata.rs @@ -332,10 +332,7 @@ impl DashboardMetadataInterface for MockDb { let index_to_remove = dashboard_metadata .iter() .position(|metadata_inner| { - metadata_inner - .user_id - .as_deref() - .map_or(false, |user_id_inner| user_id_inner == user_id) + metadata_inner.user_id.as_deref() == Some(user_id) && metadata_inner.merchant_id == *merchant_id && metadata_inner.data_key == data_key }) diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index 6bb7de1b7d..33fcece85a 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -734,6 +734,7 @@ mod tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 8eec7f0416..22e2f10c71 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; use masking::Secret; use redis_interface::{errors::RedisError, RedisConnectionPool, RedisEntryId}; -use router_env::logger; +use router_env::{instrument, logger, tracing}; use scheduler::{ db::{process_tracker::ProcessTrackerInterface, queue::QueueInterface}, SchedulerInterface, @@ -55,6 +55,7 @@ use crate::{ authentication::AuthenticationInterface, authorization::AuthorizationInterface, business_profile::ProfileInterface, + callback_mapper::CallbackMapperInterface, capture::CaptureInterface, cards_info::CardsInfoInterface, configs::ConfigInterface, @@ -3209,6 +3210,16 @@ impl UserRoleInterface for KafkaStore { self.diesel_store.list_user_roles_by_user_id(payload).await } + async fn list_user_roles_by_user_id_across_tenants( + &self, + user_id: &str, + limit: Option, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_user_roles_by_user_id_across_tenants(user_id, limit) + .await + } + async fn list_user_roles_by_org_id<'a>( &self, payload: ListUserRolesByOrgIdPayload<'a>, @@ -3606,9 +3617,10 @@ impl RoleInterface for KafkaStore { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { self.diesel_store - .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id) + .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id, tenant_id) .await } @@ -3617,19 +3629,21 @@ impl RoleInterface for KafkaStore { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { self.diesel_store - .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id) + .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id) .await } - async fn find_by_role_id_and_org_id( + async fn find_by_role_id_org_id_tenant_id( &self, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { self.diesel_store - .find_by_role_id_and_org_id(role_id, org_id) + .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id) .await } @@ -3654,19 +3668,23 @@ impl RoleInterface for KafkaStore { &self, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult, errors::StorageError> { - self.diesel_store.list_all_roles(merchant_id, org_id).await + self.diesel_store + .list_all_roles(merchant_id, org_id, tenant_id) + .await } async fn list_roles_for_org_by_parameters( &self, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, entity_type: Option, limit: Option, ) -> CustomResult, errors::StorageError> { self.diesel_store - .list_roles_for_org_by_parameters(org_id, merchant_id, entity_type, limit) + .list_roles_for_org_by_parameters(tenant_id, org_id, merchant_id, entity_type, limit) .await } } @@ -3872,3 +3890,24 @@ impl ThemeInterface for KafkaStore { .await } } + +#[async_trait::async_trait] +impl CallbackMapperInterface for KafkaStore { + #[instrument(skip_all)] + async fn insert_call_back_mapper( + &self, + call_back_mapper: domain::CallbackMapper, + ) -> CustomResult { + self.diesel_store + .insert_call_back_mapper(call_back_mapper) + .await + } + + #[instrument(skip_all)] + async fn find_call_back_mapper_by_id( + &self, + id: &str, + ) -> CustomResult { + self.diesel_store.find_call_back_mapper_by_id(id).await + } +} diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 0abbccd2cb..ba654b4fce 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -627,7 +627,12 @@ impl MerchantConnectorAccountInterface for Store { for (merchant_connector_account, update_merchant_connector_account) in merchant_connector_accounts { + #[cfg(feature = "v1")] let _connector_name = merchant_connector_account.connector_name.clone(); + + #[cfg(feature = "v2")] + let _connector_name = merchant_connector_account.connector_name.to_string(); + let _profile_id = merchant_connector_account.profile_id.clone(); let _merchant_id = merchant_connector_account.merchant_id.clone(); @@ -791,7 +796,7 @@ impl MerchantConnectorAccountInterface for Store { merchant_connector_account: storage::MerchantConnectorAccountUpdateInternal, key_store: &domain::MerchantKeyStore, ) -> CustomResult { - let _connector_name = this.connector_name.clone(); + let _connector_name = this.connector_name; let _profile_id = this.profile_id.clone(); let _merchant_id = this.merchant_id.clone(); @@ -1561,6 +1566,7 @@ mod merchant_connector_account_cache_tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -1746,6 +1752,7 @@ mod merchant_connector_account_cache_tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -1805,7 +1812,7 @@ mod merchant_connector_account_cache_tests { let mca = domain::MerchantConnectorAccount { id: id.clone(), merchant_id: merchant_id.clone(), - connector_name: "stripe".to_string(), + connector_name: common_enums::connector_enums::Connector::Stripe, connector_account_details: domain::types::crypto_operation( key_manager_state, type_name!(domain::MerchantConnectorAccount), diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 9f12ec8e8f..aaeba6085a 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -350,6 +350,7 @@ mod tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index 3a5c8b7da1..e07821cd9a 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -437,6 +437,8 @@ mod storage { organization_id: new.organization_id.clone(), connector_refund_data: new.connector_refund_data.clone(), connector_transaction_data: new.connector_transaction_data.clone(), + unified_code: None, + unified_message: None, }; let field = format!( @@ -932,6 +934,8 @@ impl RefundInterface for MockDb { organization_id: new.organization_id, connector_refund_data: new.connector_refund_data, connector_transaction_data: new.connector_transaction_data, + unified_code: None, + unified_message: None, }; refunds.push(refund.clone()); Ok(refund) @@ -1132,7 +1136,7 @@ impl RefundInterface for MockDb { || refund .merchant_connector_id .as_ref() - .map_or(false, |id| unique_merchant_connector_ids.contains(id)) + .is_some_and(|id| unique_merchant_connector_ids.contains(id)) }) .filter(|refund| { unique_currencies.is_empty() || unique_currencies.contains(&refund.currency) @@ -1338,7 +1342,7 @@ impl RefundInterface for MockDb { || refund .merchant_connector_id .as_ref() - .map_or(false, |id| unique_merchant_connector_ids.contains(id)) + .is_some_and(|id| unique_merchant_connector_ids.contains(id)) }) .filter(|refund| { unique_currencies.is_empty() || unique_currencies.contains(&refund.currency) diff --git a/crates/router/src/db/relay.rs b/crates/router/src/db/relay.rs index 46259679c5..2ead84019d 100644 --- a/crates/router/src/db/relay.rs +++ b/crates/router/src/db/relay.rs @@ -35,6 +35,14 @@ pub trait RelayInterface { merchant_key_store: &domain::MerchantKeyStore, relay_id: &common_utils::id_type::RelayId, ) -> CustomResult; + + async fn find_relay_by_profile_id_connector_reference_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + profile_id: &common_utils::id_type::ProfileId, + connector_reference_id: &str, + ) -> CustomResult; } #[async_trait::async_trait] @@ -105,6 +113,30 @@ impl RelayInterface for Store { .await .change_context(errors::StorageError::DecryptionError) } + + async fn find_relay_by_profile_id_connector_reference_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + profile_id: &common_utils::id_type::ProfileId, + connector_reference_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + diesel_models::relay::Relay::find_by_profile_id_connector_reference_id( + &conn, + profile_id, + connector_reference_id, + ) + .await + .map_err(|error| report!(errors::StorageError::from(error)))? + .convert( + key_manager_state, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError) + } } #[async_trait::async_trait] @@ -136,6 +168,16 @@ impl RelayInterface for MockDb { ) -> CustomResult { Err(errors::StorageError::MockDbError)? } + + async fn find_relay_by_profile_id_connector_reference_id( + &self, + _key_manager_state: &KeyManagerState, + _merchant_key_store: &domain::MerchantKeyStore, + _profile_id: &common_utils::id_type::ProfileId, + _connector_reference_id: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } } #[async_trait::async_trait] @@ -178,4 +220,21 @@ impl RelayInterface for KafkaStore { .find_relay_by_id(key_manager_state, merchant_key_store, relay_id) .await } + + async fn find_relay_by_profile_id_connector_reference_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + profile_id: &common_utils::id_type::ProfileId, + connector_reference_id: &str, + ) -> CustomResult { + self.diesel_store + .find_relay_by_profile_id_connector_reference_id( + key_manager_state, + merchant_key_store, + profile_id, + connector_reference_id, + ) + .await + } } diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs index 877a4c5407..1006c33aaa 100644 --- a/crates/router/src/db/role.rs +++ b/crates/router/src/db/role.rs @@ -29,6 +29,7 @@ pub trait RoleInterface { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult; async fn find_role_by_role_id_in_lineage( @@ -36,12 +37,14 @@ pub trait RoleInterface { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult; - async fn find_by_role_id_and_org_id( + async fn find_by_role_id_org_id_tenant_id( &self, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult; async fn update_role_by_role_id( @@ -59,10 +62,12 @@ pub trait RoleInterface { &self, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult, errors::StorageError>; async fn list_roles_for_org_by_parameters( &self, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, entity_type: Option, @@ -101,11 +106,18 @@ impl RoleInterface for Store { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::Role::find_by_role_id_in_merchant_scope(&conn, role_id, merchant_id, org_id) - .await - .map_err(|error| report!(errors::StorageError::from(error))) + storage::Role::find_by_role_id_in_merchant_scope( + &conn, + role_id, + merchant_id, + org_id, + tenant_id, + ) + .await + .map_err(|error| report!(errors::StorageError::from(error))) } #[instrument(skip_all)] @@ -114,21 +126,23 @@ impl RoleInterface for Store { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id) + storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id, tenant_id) .await .map_err(|error| report!(errors::StorageError::from(error))) } #[instrument(skip_all)] - async fn find_by_role_id_and_org_id( + async fn find_by_role_id_org_id_tenant_id( &self, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::Role::find_by_role_id_and_org_id(&conn, role_id, org_id) + storage::Role::find_by_role_id_org_id_tenant_id(&conn, role_id, org_id, tenant_id) .await .map_err(|error| report!(errors::StorageError::from(error))) } @@ -161,9 +175,10 @@ impl RoleInterface for Store { &self, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult, errors::StorageError> { let conn = connection::pg_connection_read(self).await?; - storage::Role::list_roles(&conn, merchant_id, org_id) + storage::Role::list_roles(&conn, merchant_id, org_id, tenant_id) .await .map_err(|error| report!(errors::StorageError::from(error))) } @@ -171,6 +186,7 @@ impl RoleInterface for Store { #[instrument(skip_all)] async fn list_roles_for_org_by_parameters( &self, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, entity_type: Option, @@ -179,6 +195,7 @@ impl RoleInterface for Store { let conn = connection::pg_connection_read(self).await?; storage::Role::generic_roles_list_for_org( &conn, + tenant_id.to_owned(), org_id.to_owned(), merchant_id.cloned(), entity_type, @@ -217,6 +234,7 @@ impl RoleInterface for MockDb { created_at: role.created_at, last_modified_at: role.last_modified_at, last_modified_by: role.last_modified_by, + tenant_id: role.tenant_id, }; roles.push(role.clone()); Ok(role) @@ -245,12 +263,14 @@ impl RoleInterface for MockDb { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let roles = self.roles.lock().await; roles .iter() .find(|role| { role.role_id == role_id + && (role.tenant_id == *tenant_id) && (role.merchant_id == *merchant_id || (role.org_id == *org_id && role.scope == enums::RoleScope::Organization)) }) @@ -269,12 +289,14 @@ impl RoleInterface for MockDb { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let roles = self.roles.lock().await; roles .iter() .find(|role| { role.role_id == role_id + && (role.tenant_id == *tenant_id) && role.org_id == *org_id && ((role.scope == enums::RoleScope::Organization) || (role.merchant_id == *merchant_id @@ -290,15 +312,18 @@ impl RoleInterface for MockDb { ) } - async fn find_by_role_id_and_org_id( + async fn find_by_role_id_org_id_tenant_id( &self, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { let roles = self.roles.lock().await; roles .iter() - .find(|role| role.role_id == role_id && role.org_id == *org_id) + .find(|role| { + role.role_id == role_id && role.org_id == *org_id && role.tenant_id == *tenant_id + }) .cloned() .ok_or( errors::StorageError::ValueNotFound(format!( @@ -361,15 +386,17 @@ impl RoleInterface for MockDb { &self, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult, errors::StorageError> { let roles = self.roles.lock().await; let roles_list: Vec<_> = roles .iter() .filter(|role| { - role.merchant_id == *merchant_id - || (role.org_id == *org_id - && role.scope == diesel_models::enums::RoleScope::Organization) + role.tenant_id == *tenant_id + && (role.merchant_id == *merchant_id + || (role.org_id == *org_id + && role.scope == diesel_models::enums::RoleScope::Organization)) }) .cloned() .collect(); @@ -388,6 +415,7 @@ impl RoleInterface for MockDb { #[instrument(skip_all)] async fn list_roles_for_org_by_parameters( &self, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, entity_type: Option, @@ -403,7 +431,10 @@ impl RoleInterface for MockDb { None => true, }; - matches_merchant && role.org_id == *org_id && Some(role.entity_type) == entity_type + matches_merchant + && role.org_id == *org_id + && role.tenant_id == *tenant_id + && Some(role.entity_type) == entity_type }) .take(limit_usize) .cloned() diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index 0da5189832..1df6160f81 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -79,6 +79,12 @@ pub trait UserRoleInterface { payload: ListUserRolesByUserIdPayload<'a>, ) -> CustomResult, errors::StorageError>; + async fn list_user_roles_by_user_id_across_tenants( + &self, + user_id: &str, + limit: Option, + ) -> CustomResult, errors::StorageError>; + async fn list_user_roles_by_org_id<'a>( &self, payload: ListUserRolesByOrgIdPayload<'a>, @@ -195,6 +201,21 @@ impl UserRoleInterface for Store { .map_err(|error| report!(errors::StorageError::from(error))) } + async fn list_user_roles_by_user_id_across_tenants( + &self, + user_id: &str, + limit: Option, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + storage::UserRole::list_user_roles_by_user_id_across_tenants( + &conn, + user_id.to_owned(), + limit, + ) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + } + async fn list_user_roles_by_org_id<'a>( &self, payload: ListUserRolesByOrgIdPayload<'a>, @@ -472,6 +493,26 @@ impl UserRoleInterface for MockDb { Ok(filtered_roles) } + async fn list_user_roles_by_user_id_across_tenants( + &self, + user_id: &str, + limit: Option, + ) -> CustomResult, errors::StorageError> { + let user_roles = self.user_roles.lock().await; + + let filtered_roles: Vec<_> = user_roles + .iter() + .filter(|role| role.user_id == user_id) + .cloned() + .collect(); + + if let Some(Ok(limit)) = limit.map(|val| val.try_into()) { + return Ok(filtered_roles.into_iter().take(limit).collect()); + } + + Ok(filtered_roles) + } + async fn list_user_roles_by_org_id<'a>( &self, payload: ListUserRolesByOrgIdPayload<'a>, diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 4fe9318f64..9febfa5e46 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -142,6 +142,7 @@ pub fn mk_app( .service(routes::Customers::server(state.clone())) .service(routes::Configs::server(state.clone())) .service(routes::MerchantConnectorAccount::server(state.clone())) + .service(routes::RelayWebhooks::server(state.clone())) .service(routes::Webhooks::server(state.clone())) .service(routes::Relay::server(state.clone())); @@ -236,19 +237,9 @@ pub async fn start_server(conf: settings::Settings) -> Applicatio logger::debug!(startup_config=?conf); let server = conf.server.clone(); let (tx, rx) = oneshot::channel(); - let api_client = Box::new( - services::ProxyClient::new( - conf.proxy.clone(), - services::proxy_bypass_urls( - conf.key_manager.get_inner(), - &conf.locker, - &conf.proxy.bypass_proxy_urls, - ), - ) - .map_err(|error| { - errors::ApplicationError::ApiClientError(error.current_context().clone()) - })?, - ); + let api_client = Box::new(services::ProxyClient::new(&conf.proxy).map_err(|error| { + errors::ApplicationError::ApiClientError(error.current_context().clone()) + })?); let state = Box::pin(AppState::new(conf, tx, api_client)).await; let request_body_limit = server.request_body_limit; diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index 462861d331..22f5983e4c 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -69,7 +69,7 @@ pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, - Profile, ProfileNew, Refunds, Relay, SessionState, User, Webhooks, + Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f9dbec7745..fffb3eab09 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -33,7 +33,7 @@ use super::dummy_connector::*; #[cfg(all(any(feature = "v1", feature = "v2"), feature = "oltp"))] use super::ephemeral_key::*; #[cfg(any(feature = "olap", feature = "oltp"))] -use super::payment_methods::*; +use super::payment_methods; #[cfg(feature = "payouts")] use super::payout_link::*; #[cfg(feature = "payouts")] @@ -59,7 +59,7 @@ use super::{ #[cfg(feature = "v1")] use super::{apple_pay_certificates_migration, blocklist, payment_link, webhook_events}; #[cfg(any(feature = "olap", feature = "oltp"))] -use super::{configs::*, customers::*, payments}; +use super::{configs::*, customers, payments}; #[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))] use super::{mandates::*, refunds::*}; #[cfg(feature = "olap")] @@ -112,6 +112,7 @@ pub struct SessionState { pub opensearch_client: Arc, pub grpc_client: Arc, pub theme_storage_client: Arc, + pub locale: String, } impl scheduler::SchedulerSessionState for SessionState { fn get_db(&self) -> Box { @@ -458,6 +459,7 @@ impl AppState { pub fn get_session_state( self: Arc, tenant: &id_type::TenantId, + locale: Option, err: F, ) -> Result where @@ -484,6 +486,7 @@ impl AppState { opensearch_client: Arc::clone(&self.opensearch_client), grpc_client: Arc::clone(&self.grpc_client), theme_storage_client: self.theme_storage_client.clone(), + locale: locale.unwrap_or(common_utils::consts::DEFAULT_LOCALE.to_string()), }) } } @@ -987,24 +990,25 @@ impl Customers { let mut route = web::scope("/v2/customers").app_data(web::Data::new(state)); #[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))] { - route = route.service(web::resource("/list").route(web::get().to(customers_list))) + route = route + .service(web::resource("/list").route(web::get().to(customers::customers_list))) } #[cfg(all(feature = "oltp", feature = "v2", feature = "customer_v2"))] { route = route - .service(web::resource("").route(web::post().to(customers_create))) + .service(web::resource("").route(web::post().to(customers::customers_create))) .service( web::resource("/{id}") - .route(web::put().to(customers_update)) - .route(web::get().to(customers_retrieve)) - .route(web::delete().to(customers_delete)), + .route(web::put().to(customers::customers_update)) + .route(web::get().to(customers::customers_retrieve)) + .route(web::delete().to(customers::customers_delete)), ) } #[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2"))] { route = route.service( web::resource("/{customer_id}/saved-payment-methods") - .route(web::get().to(list_customer_payment_method_api)), + .route(web::get().to(payment_methods::list_customer_payment_method_api)), ); } route @@ -1026,32 +1030,33 @@ impl Customers { route = route .service( web::resource("/{customer_id}/mandates") - .route(web::get().to(get_customer_mandates)), + .route(web::get().to(customers::get_customer_mandates)), ) - .service(web::resource("/list").route(web::get().to(customers_list))) + .service(web::resource("/list").route(web::get().to(customers::customers_list))) } #[cfg(feature = "oltp")] { route = route - .service(web::resource("").route(web::post().to(customers_create))) + .service(web::resource("").route(web::post().to(customers::customers_create))) .service( - web::resource("/payment_methods") - .route(web::get().to(list_customer_payment_method_api_client)), + web::resource("/payment_methods").route( + web::get().to(payment_methods::list_customer_payment_method_api_client), + ), ) .service( web::resource("/{customer_id}/payment_methods") - .route(web::get().to(list_customer_payment_method_api)), + .route(web::get().to(payment_methods::list_customer_payment_method_api)), ) .service( web::resource("/{customer_id}/payment_methods/{payment_method_id}/default") - .route(web::post().to(default_payment_method_set_api)), + .route(web::post().to(payment_methods::default_payment_method_set_api)), ) .service( web::resource("/{customer_id}") - .route(web::get().to(customers_retrieve)) - .route(web::post().to(customers_update)) - .route(web::delete().to(customers_delete)), + .route(web::get().to(customers::customers_retrieve)) + .route(web::post().to(customers::customers_update)) + .route(web::delete().to(customers::customers_delete)), ) } @@ -1150,25 +1155,35 @@ impl PaymentMethods { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/v2/payment-methods").app_data(web::Data::new(state)); route = route - .service(web::resource("").route(web::post().to(create_payment_method_api))) - .service( - web::resource("/create-intent") - .route(web::post().to(create_payment_method_intent_api)), - ) .service( - web::resource("/{id}/confirm-intent") - .route(web::post().to(confirm_payment_method_intent_api)), + web::resource("").route(web::post().to(payment_methods::create_payment_method_api)), ) .service( - web::resource("/{id}/update-saved-payment-method") - .route(web::patch().to(payment_method_update_api)), - ) - .service( - web::resource("/{id}") - .route(web::get().to(payment_method_retrieve_api)) - .route(web::delete().to(payment_method_delete_api)), + web::resource("/create-intent") + .route(web::post().to(payment_methods::create_payment_method_intent_api)), ); + route = route.service( + web::scope("/{id}") + .service( + web::resource("") + .route(web::get().to(payment_methods::payment_method_retrieve_api)) + .route(web::delete().to(payment_methods::payment_method_delete_api)), + ) + .service( + web::resource("/list-enabled-payment-methods") + .route(web::get().to(payment_methods::list_payment_methods_enabled)), + ) + .service( + web::resource("/confirm-intent") + .route(web::post().to(payment_methods::confirm_payment_method_intent_api)), + ) + .service( + web::resource("/update-saved-payment-method") + .route(web::put().to(payment_methods::payment_method_update_api)), + ), + ); + route } } @@ -1184,44 +1199,49 @@ impl PaymentMethods { let mut route = web::scope("/payment_methods").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { - route = route.service( - web::resource("/filter") - .route(web::get().to(list_countries_currencies_for_connector_payment_method)), - ); + route = + route.service(web::resource("/filter").route( + web::get().to( + payment_methods::list_countries_currencies_for_connector_payment_method, + ), + )); } #[cfg(feature = "oltp")] { route = route .service( web::resource("") - .route(web::post().to(create_payment_method_api)) - .route(web::get().to(list_payment_method_api)), // TODO : added for sdk compatibility for now, need to deprecate this later + .route(web::post().to(payment_methods::create_payment_method_api)) + .route(web::get().to(payment_methods::list_payment_method_api)), // TODO : added for sdk compatibility for now, need to deprecate this later ) .service( - web::resource("/migrate").route(web::post().to(migrate_payment_method_api)), + web::resource("/migrate") + .route(web::post().to(payment_methods::migrate_payment_method_api)), ) .service( - web::resource("/migrate-batch").route(web::post().to(migrate_payment_methods)), + web::resource("/migrate-batch") + .route(web::post().to(payment_methods::migrate_payment_methods)), ) .service( - web::resource("/collect").route(web::post().to(initiate_pm_collect_link_flow)), + web::resource("/collect") + .route(web::post().to(payment_methods::initiate_pm_collect_link_flow)), ) .service( web::resource("/collect/{merchant_id}/{collect_id}") - .route(web::get().to(render_pm_collect_link)), + .route(web::get().to(payment_methods::render_pm_collect_link)), ) .service( web::resource("/{payment_method_id}") - .route(web::get().to(payment_method_retrieve_api)) - .route(web::delete().to(payment_method_delete_api)), + .route(web::get().to(payment_methods::payment_method_retrieve_api)) + .route(web::delete().to(payment_methods::payment_method_delete_api)), ) .service( web::resource("/{payment_method_id}/update") - .route(web::post().to(payment_method_update_api)), + .route(web::post().to(payment_methods::payment_method_update_api)), ) .service( web::resource("/{payment_method_id}/save") - .route(web::post().to(save_payment_method_api)), + .route(web::post().to(payment_methods::save_payment_method_api)), ) .service( web::resource("/auth/link").route(web::post().to(pm_auth::link_token_create)), @@ -1424,7 +1444,8 @@ impl MerchantConnectorAccount { #[cfg(feature = "oltp")] { route = route.service( - web::resource("/payment_methods").route(web::get().to(list_payment_method_api)), + web::resource("/payment_methods") + .route(web::get().to(payment_methods::list_payment_method_api)), ); } route @@ -1512,6 +1533,20 @@ impl Webhooks { } } +pub struct RelayWebhooks; + +#[cfg(feature = "oltp")] +impl RelayWebhooks { + pub fn server(state: AppState) -> Scope { + use api_models::webhooks as webhook_type; + web::scope("/webhooks/relay") + .app_data(web::Data::new(state)) + .service(web::resource("/{merchant_id}/{connector_id}").route( + web::post().to(receive_incoming_relay_webhook::), + )) + } +} + #[cfg(all(feature = "oltp", feature = "v2"))] impl Webhooks { pub fn server(config: AppState) -> Scope { diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs index 1935b8cd00..c919bfcd40 100644 --- a/crates/router/src/routes/disputes.rs +++ b/crates/router/src/routes/disputes.rs @@ -168,6 +168,7 @@ pub async fn retrieve_disputes_list_profile( .await } +#[cfg(feature = "v1")] /// Disputes - Disputes Filters #[utoipa::path( get, diff --git a/crates/router/src/routes/feature_matrix.rs b/crates/router/src/routes/feature_matrix.rs index 08dd7d0d7b..66a9968021 100644 --- a/crates/router/src/routes/feature_matrix.rs +++ b/crates/router/src/routes/feature_matrix.rs @@ -98,7 +98,7 @@ fn build_connector_feature_details( .get_supported_webhook_flows() .map(|webhook_flows| webhook_flows.to_vec()); feature_matrix::ConnectorFeatureMatrixResponse { - name: connector_name, + name: connector_name.to_uppercase(), description: connector_about.map(|about| about.description.clone()), category: connector_about.map(|about| about.connector_type), supported_webhook_flows, @@ -142,6 +142,7 @@ fn build_payment_method_wise_feature_details( mandates: feature_metadata.mandates, refunds: feature_metadata.refunds, supported_capture_methods: feature_metadata.supported_capture_methods.clone(), + payment_method_specific_features: feature_metadata.specific_features.clone(), supported_countries, supported_currencies, } diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 9d7ae1874c..6550778619 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -171,6 +171,7 @@ impl From for ApiIdentifier { Flow::FrmFulfillment | Flow::IncomingWebhookReceive + | Flow::IncomingRelayWebhookReceive | Flow::WebhookEventInitialDeliveryAttemptList | Flow::WebhookEventDeliveryAttemptList | Flow::WebhookEventDeliveryRetry => Self::Webhooks, diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 5920c001d3..edb33f4876 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -141,3 +141,6 @@ histogram_metric_f64!(GENERATE_NETWORK_TOKEN_TIME, GLOBAL_METER); histogram_metric_f64!(FETCH_NETWORK_TOKEN_TIME, GLOBAL_METER); histogram_metric_f64!(DELETE_NETWORK_TOKEN_TIME, GLOBAL_METER); histogram_metric_f64!(CHECK_NETWORK_TOKEN_STATUS_TIME, GLOBAL_METER); + +// A counter to indicate allowed payment method types mismatch +counter_metric!(PAYMENT_METHOD_TYPES_MISCONFIGURATION_METRIC, GLOBAL_METER); diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index 71f10fe73e..361367c7d2 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -65,7 +65,6 @@ pub async fn initiate_payment_link( payment_id, merchant_id: merchant_id.clone(), }; - let headers = req.headers(); Box::pin(api::server_wrap( flow, state, @@ -78,7 +77,6 @@ pub async fn initiate_payment_link( auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), - headers, ) }, &crate::services::authentication::MerchantIdAuth(merchant_id), @@ -183,7 +181,6 @@ pub async fn payment_link_status( payment_id, merchant_id: merchant_id.clone(), }; - let headers = req.headers(); Box::pin(api::server_wrap( flow, state, @@ -196,7 +193,6 @@ pub async fn payment_link_status( auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), - headers, ) }, &crate::services::authentication::MerchantIdAuth(merchant_id), diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index d8dfb73206..c8e6303562 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -129,13 +129,47 @@ pub async fn create_payment_method_intent_api( .await } +/// This struct is used internally only +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct PaymentMethodIntentConfirmInternal { + pub id: id_type::GlobalPaymentMethodId, + pub payment_method_type: common_enums::PaymentMethod, + pub payment_method_subtype: common_enums::PaymentMethodType, + pub customer_id: Option, + pub payment_method_data: payment_methods::PaymentMethodCreateData, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for payment_methods::PaymentMethodIntentConfirm { + fn from(item: PaymentMethodIntentConfirmInternal) -> Self { + Self { + 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(), + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl common_utils::events::ApiEventMetric for PaymentMethodIntentConfirmInternal { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::PaymentMethod { + payment_method_id: self.id.clone(), + payment_method_type: Some(self.payment_method_type), + payment_method_subtype: Some(self.payment_method_subtype), + }) + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsCreate))] pub async fn confirm_payment_method_intent_api( state: web::Data, req: HttpRequest, json_payload: web::Json, - path: web::Path, + path: web::Path, ) -> HttpResponse { let flow = Flow::PaymentMethodsCreate; let pm_id = path.into_inner(); @@ -146,8 +180,8 @@ pub async fn confirm_payment_method_intent_api( Err(e) => return api::log_and_return_error_response(e), }; - let inner_payload = payment_methods::PaymentMethodIntentConfirmInternal { - id: pm_id.clone(), + let inner_payload = PaymentMethodIntentConfirmInternal { + id: pm_id.to_owned(), payment_method_type: payload.payment_method_type, payment_method_subtype: payload.payment_method_subtype, customer_id: payload.customer_id.to_owned(), @@ -178,6 +212,41 @@ pub async fn confirm_payment_method_intent_api( .await } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsList))] +pub async fn list_payment_methods_enabled( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::PaymentMethodsList; + let payment_method_id = path.into_inner(); + + let auth = match auth::is_ephemeral_or_publishible_auth(req.headers()) { + Ok(auth) => auth, + Err(e) => return api::log_and_return_error_response(e), + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + payment_method_id, + |state, auth: auth::AuthenticationData, payment_method_id, _| { + payment_methods_routes::list_payment_methods_enabled( + state, + auth.merchant_account, + auth.key_store, + auth.profile, + payment_method_id, + ) + }, + &*auth, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsUpdate))] pub async fn payment_method_update_api( @@ -521,7 +590,7 @@ pub async fn list_customer_payment_method_api( state: web::Data, customer_id: web::Path, req: HttpRequest, - query_payload: web::Query, + query_payload: web::Query, ) -> HttpResponse { let flow = Flow::CustomerPaymentMethodsList; let payload = query_payload.into_inner(); diff --git a/crates/router/src/routes/payout_link.rs b/crates/router/src/routes/payout_link.rs index 25528b21ed..0234b4fca8 100644 --- a/crates/router/src/routes/payout_link.rs +++ b/crates/router/src/routes/payout_link.rs @@ -1,14 +1,12 @@ use actix_web::{web, Responder}; use api_models::payouts::PayoutLinkInitiateRequest; -use common_utils::consts::DEFAULT_LOCALE; use router_env::Flow; use crate::{ core::{api_locking, payout_link::*}, - headers::ACCEPT_LANGUAGE, services::{ api, - authentication::{self as auth, get_header_value_by_key}, + authentication::{self as auth}, }, AppState, }; @@ -25,25 +23,13 @@ pub async fn render_payout_link( payout_id, }; let headers = req.headers(); - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers) - .ok() - .flatten() - .map(|val| val.to_string()) - .unwrap_or(DEFAULT_LOCALE.to_string()); Box::pin(api::server_wrap( flow, state, &req, payload.clone(), |state, auth, req, _| { - initiate_payout_link( - state, - auth.merchant_account, - auth.key_store, - req, - headers, - locale.clone(), - ) + initiate_payout_link(state, auth.merchant_account, auth.key_store, req, headers) }, &auth::MerchantIdAuth(merchant_id), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs index 2329a48ef2..4044630b4d 100644 --- a/crates/router/src/routes/payouts.rs +++ b/crates/router/src/routes/payouts.rs @@ -1,31 +1,20 @@ use actix_web::{ body::{BoxBody, MessageBody}, - http::header::HeaderMap, web, HttpRequest, HttpResponse, Responder, }; -use common_utils::consts; use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, payouts::*}, - headers::ACCEPT_LANGUAGE, services::{ api, - authentication::{self as auth, get_header_value_by_key}, + authentication::{self as auth}, authorization::permissions::Permission, }, types::api::payouts as payout_types, }; -fn get_locale_from_header(headers: &HeaderMap) -> String { - get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers) - .ok() - .flatten() - .map(|val| val.to_string()) - .unwrap_or(consts::DEFAULT_LOCALE.to_string()) -} - /// Payouts - Create #[instrument(skip_all, fields(flow = ?Flow::PayoutsCreate))] pub async fn payouts_create( @@ -34,7 +23,6 @@ pub async fn payouts_create( json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PayoutsCreate; - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -42,7 +30,7 @@ pub async fn payouts_create( &req, json_payload.into_inner(), |state, auth: auth::AuthenticationData, req, _| { - payouts_create_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_create_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -65,7 +53,6 @@ pub async fn payouts_retrieve( merchant_id: query_params.merchant_id.to_owned(), }; let flow = Flow::PayoutsRetrieve; - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -79,7 +66,6 @@ pub async fn payouts_retrieve( auth.profile_id, auth.key_store, req, - &locale, ) }, auth::auth_type( @@ -102,7 +88,6 @@ pub async fn payouts_update( json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PayoutsUpdate; - let locale = get_locale_from_header(req.headers()); let payout_id = path.into_inner(); let mut payout_update_payload = json_payload.into_inner(); payout_update_payload.payout_id = Some(payout_id); @@ -112,7 +97,7 @@ pub async fn payouts_update( &req, payout_update_payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_update_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_update_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -138,7 +123,6 @@ pub async fn payouts_confirm( Ok(auth) => auth, Err(e) => return api::log_and_return_error_response(e), }; - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -146,7 +130,7 @@ pub async fn payouts_confirm( &req, payload, |state, auth, req, _| { - payouts_confirm_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_confirm_core(state, auth.merchant_account, auth.key_store, req) }, &*auth_type, api_locking::LockAction::NotApplicable, @@ -165,7 +149,6 @@ pub async fn payouts_cancel( let flow = Flow::PayoutsCancel; let mut payload = json_payload.into_inner(); payload.payout_id = path.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -173,7 +156,7 @@ pub async fn payouts_cancel( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_cancel_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_cancel_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -191,7 +174,6 @@ pub async fn payouts_fulfill( let flow = Flow::PayoutsFulfill; let mut payload = json_payload.into_inner(); payload.payout_id = path.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -199,7 +181,7 @@ pub async fn payouts_fulfill( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -217,7 +199,6 @@ pub async fn payouts_list( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -225,14 +206,7 @@ pub async fn payouts_list( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_list_core( - state, - auth.merchant_account, - None, - auth.key_store, - req, - &locale, - ) + payouts_list_core(state, auth.merchant_account, None, auth.key_store, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -256,7 +230,6 @@ pub async fn payouts_list_profile( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -270,7 +243,6 @@ pub async fn payouts_list_profile( auth.profile_id.map(|profile_id| vec![profile_id]), auth.key_store, req, - &locale, ) }, auth::auth_type( @@ -295,7 +267,6 @@ pub async fn payouts_list_by_filter( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -303,14 +274,7 @@ pub async fn payouts_list_by_filter( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_filtered_list_core( - state, - auth.merchant_account, - None, - auth.key_store, - req, - &locale, - ) + payouts_filtered_list_core(state, auth.merchant_account, None, auth.key_store, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -334,7 +298,6 @@ pub async fn payouts_list_by_filter_profile( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -348,7 +311,6 @@ pub async fn payouts_list_by_filter_profile( auth.profile_id.map(|profile_id| vec![profile_id]), auth.key_store, req, - &locale, ) }, auth::auth_type( @@ -373,7 +335,6 @@ pub async fn payouts_list_available_filters_for_merchant( ) -> HttpResponse { let flow = Flow::PayoutsFilter; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -381,7 +342,7 @@ pub async fn payouts_list_available_filters_for_merchant( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_list_available_filters_core(state, auth.merchant_account, None, req, &locale) + payouts_list_available_filters_core(state, auth.merchant_account, None, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -405,7 +366,6 @@ pub async fn payouts_list_available_filters_for_profile( ) -> HttpResponse { let flow = Flow::PayoutsFilter; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -418,7 +378,6 @@ pub async fn payouts_list_available_filters_for_profile( auth.merchant_account, auth.profile_id.map(|profile_id| vec![profile_id]), req, - &locale, ) }, auth::auth_type( diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index d075048ddb..1af22d0338 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -197,6 +197,7 @@ pub async fn set_dashboard_metadata( .await } +#[cfg(feature = "v1")] pub async fn get_multiple_dashboard_metadata( state: web::Data, req: HttpRequest, diff --git a/crates/router/src/routes/webhooks.rs b/crates/router/src/routes/webhooks.rs index 5427ac34b4..1b3f28fd25 100644 --- a/crates/router/src/routes/webhooks.rs +++ b/crates/router/src/routes/webhooks.rs @@ -36,6 +36,7 @@ pub async fn receive_incoming_webhook( auth.key_store, &connector_id_or_name, body.clone(), + false, ) }, &auth::MerchantIdAuth(merchant_id), @@ -44,6 +45,89 @@ pub async fn receive_incoming_webhook( .await } +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::IncomingRelayWebhookReceive))] +pub async fn receive_incoming_relay_webhook( + state: web::Data, + req: HttpRequest, + body: web::Bytes, + path: web::Path<( + common_utils::id_type::MerchantId, + common_utils::id_type::MerchantConnectorAccountId, + )>, +) -> impl Responder { + let flow = Flow::IncomingWebhookReceive; + let (merchant_id, connector_id) = path.into_inner(); + let is_relay_webhook = true; + + Box::pin(api::server_wrap( + flow.clone(), + state, + &req, + (), + |state, auth, _, req_state| { + webhooks::incoming_webhooks_wrapper::( + &flow, + state.to_owned(), + req_state, + &req, + auth.merchant_account, + auth.key_store, + connector_id.get_string_repr(), + body.clone(), + is_relay_webhook, + ) + }, + &auth::MerchantIdAuth(merchant_id), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::IncomingRelayWebhookReceive))] +pub async fn receive_incoming_relay_webhook( + state: web::Data, + req: HttpRequest, + body: web::Bytes, + path: web::Path<( + common_utils::id_type::MerchantId, + common_utils::id_type::ProfileId, + common_utils::id_type::MerchantConnectorAccountId, + )>, +) -> impl Responder { + let flow = Flow::IncomingWebhookReceive; + let (merchant_id, profile_id, connector_id) = path.into_inner(); + let is_relay_webhook = true; + + Box::pin(api::server_wrap( + flow.clone(), + state, + &req, + (), + |state, auth, _, req_state| { + webhooks::incoming_webhooks_wrapper::( + &flow, + state.to_owned(), + req_state, + &req, + auth.merchant_account, + auth.profile, + auth.key_store, + &connector_id, + body.clone(), + is_relay_webhook, + ) + }, + &auth::MerchantIdAndProfileIdAuth { + merchant_id, + profile_id, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[instrument(skip_all, fields(flow = ?Flow::IncomingWebhookReceive))] #[cfg(feature = "v2")] pub async fn receive_incoming_webhook( @@ -75,6 +159,7 @@ pub async fn receive_incoming_webhook( auth.key_store, &connector_id, body.clone(), + false, ) }, &auth::MerchantIdAndProfileIdAuth { diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index cac856b2c4..0a56ff83f9 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -17,7 +17,7 @@ use actix_web::{ http::header::{HeaderName, HeaderValue}, web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError, }; -pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; +pub use client::{ApiClient, MockApiClient, ProxyClient}; pub use common_enums::enums::PaymentAction; pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use common_utils::{ @@ -79,6 +79,7 @@ use crate::{ generic_link_response::build_generic_link_html, }, types::{self, api, ErrorResponse}, + utils, }; pub type BoxedPaymentConnectorIntegrationInterface = @@ -415,29 +416,11 @@ pub async fn send_request( ) -> CustomResult { logger::info!(method=?request.method, headers=?request.headers, payload=?request.body, ?request); - let url = reqwest::Url::parse(&request.url) - .change_context(errors::ApiClientError::UrlEncodingFailed)?; - - #[cfg(feature = "dummy_connector")] - let should_bypass_proxy = url - .as_str() - .starts_with(&state.conf.connectors.dummyconnector.base_url) - || proxy_bypass_urls( - state.conf.key_manager.get_inner(), - &state.conf.locker, - &state.conf.proxy.bypass_proxy_urls, - ) - .contains(&url.to_string()); - #[cfg(not(feature = "dummy_connector"))] - let should_bypass_proxy = proxy_bypass_urls( - &state.conf.key_manager.get_inner(), - &state.conf.locker, - &state.conf.proxy.bypass_proxy_urls, - ) - .contains(&url.to_string()); + let url = + url::Url::parse(&request.url).change_context(errors::ApiClientError::UrlParsingFailed)?; + let client = client::create_client( &state.conf.proxy, - should_bypass_proxy, request.certificate, request.certificate_key, )?; @@ -759,12 +742,14 @@ where )? }; - let mut session_state = Arc::new(app_state.clone()).get_session_state(&tenant_id, || { - errors::ApiErrorResponse::InvalidTenant { - tenant_id: tenant_id.get_string_repr().to_string(), - } - .switch() - })?; + let locale = utils::get_locale_from_header(&incoming_request_header.clone()); + let mut session_state = + Arc::new(app_state.clone()).get_session_state(&tenant_id, Some(locale), || { + errors::ApiErrorResponse::InvalidTenant { + tenant_id: tenant_id.get_string_repr().to_string(), + } + .switch() + })?; session_state.add_request_id(request_id); let mut request_state = session_state.get_req_state(); @@ -1532,6 +1517,46 @@ pub fn build_redirection_form( "))) }} } + RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta name="viewport" content="width=device-width, initial-scale=1"; + } + + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" } + + (PreEscaped(r#""#)) + + (PreEscaped(r#" + + "#)) + + h3 style="text-align: center;" { "Please wait while we process your payment..." } + } + (PreEscaped(format!("
+ +
"))) + (PreEscaped(format!(""))) + } + } + } RedirectForm::Payme => { maud::html! { (maud::DOCTYPE) diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs index 9a496c18d9..096c5ed45d 100644 --- a/crates/router/src/services/api/client.rs +++ b/crates/router/src/services/api/client.rs @@ -10,18 +10,16 @@ use router_env::tracing_actix_web::RequestId; use super::{request::Maskable, Request}; use crate::{ - configs::settings::{Locker, Proxy}, - consts::{BASE64_ENGINE, LOCKER_HEALTH_CALL_PATH}, + configs::settings::Proxy, + consts::BASE64_ENGINE, core::errors::{ApiClientError, CustomResult}, - routes::{app::settings::KeyManagerConfig, SessionState}, + routes::SessionState, }; -static NON_PROXIED_CLIENT: OnceCell = OnceCell::new(); -static PROXIED_CLIENT: OnceCell = OnceCell::new(); +static DEFAULT_CLIENT: OnceCell = OnceCell::new(); fn get_client_builder( proxy_config: &Proxy, - should_bypass_proxy: bool, ) -> CustomResult { let mut client_builder = reqwest::Client::builder() .redirect(reqwest::redirect::Policy::none()) @@ -31,16 +29,16 @@ fn get_client_builder( .unwrap_or_default(), )); - if should_bypass_proxy { - return Ok(client_builder); - } + let proxy_exclusion_config = + reqwest::NoProxy::from_string(&proxy_config.bypass_proxy_hosts.clone().unwrap_or_default()); // Proxy all HTTPS traffic through the configured HTTPS proxy if let Some(url) = proxy_config.https_url.as_ref() { client_builder = client_builder.proxy( reqwest::Proxy::https(url) .change_context(ApiClientError::InvalidProxyConfiguration) - .attach_printable("HTTPS proxy configuration error")?, + .attach_printable("HTTPS proxy configuration error")? + .no_proxy(proxy_exclusion_config.clone()), ); } @@ -49,44 +47,35 @@ fn get_client_builder( client_builder = client_builder.proxy( reqwest::Proxy::http(url) .change_context(ApiClientError::InvalidProxyConfiguration) - .attach_printable("HTTP proxy configuration error")?, + .attach_printable("HTTP proxy configuration error")? + .no_proxy(proxy_exclusion_config), ); } Ok(client_builder) } -fn get_base_client( - proxy_config: &Proxy, - should_bypass_proxy: bool, -) -> CustomResult { - Ok(if should_bypass_proxy - || (proxy_config.http_url.is_none() && proxy_config.https_url.is_none()) - { - &NON_PROXIED_CLIENT - } else { - &PROXIED_CLIENT - } - .get_or_try_init(|| { - get_client_builder(proxy_config, should_bypass_proxy)? - .build() - .change_context(ApiClientError::ClientConstructionFailed) - .attach_printable("Failed to construct base client") - })? - .clone()) +fn get_base_client(proxy_config: &Proxy) -> CustomResult { + Ok(DEFAULT_CLIENT + .get_or_try_init(|| { + get_client_builder(proxy_config)? + .build() + .change_context(ApiClientError::ClientConstructionFailed) + .attach_printable("Failed to construct base client") + })? + .clone()) } // We may need to use outbound proxy to connect to external world. // Precedence will be the environment variables, followed by the config. pub fn create_client( proxy_config: &Proxy, - should_bypass_proxy: bool, client_certificate: Option>, client_certificate_key: Option>, ) -> CustomResult { match (client_certificate, client_certificate_key) { (Some(encoded_certificate), Some(encoded_certificate_key)) => { - let client_builder = get_client_builder(proxy_config, should_bypass_proxy)?; + let client_builder = get_client_builder(proxy_config)?; let identity = create_identity_from_certificate_and_key( encoded_certificate.clone(), @@ -105,7 +94,7 @@ pub fn create_client( .change_context(ApiClientError::ClientConstructionFailed) .attach_printable("Failed to construct client with certificate and certificate key") } - _ => get_base_client(proxy_config, should_bypass_proxy), + _ => get_base_client(proxy_config), } } @@ -145,35 +134,6 @@ pub fn create_certificate( .change_context(ApiClientError::CertificateDecodeFailed) } -pub fn proxy_bypass_urls( - key_manager: &KeyManagerConfig, - locker: &Locker, - config_whitelist: &[String], -) -> Vec { - let key_manager_host = key_manager.url.to_owned(); - let locker_host = locker.host.to_owned(); - let locker_host_rs = locker.host_rs.to_owned(); - - let proxy_list = [ - format!("{locker_host}/cards/add"), - format!("{locker_host}/cards/fingerprint"), - format!("{locker_host}/cards/retrieve"), - format!("{locker_host}/cards/delete"), - format!("{locker_host_rs}/cards/add"), - format!("{locker_host_rs}/cards/retrieve"), - format!("{locker_host_rs}/cards/delete"), - format!("{locker_host_rs}{}", LOCKER_HEALTH_CALL_PATH), - format!("{locker_host}/card/addCard"), - format!("{locker_host}/card/getCard"), - format!("{locker_host}/card/deleteCard"), - format!("{key_manager_host}/data/encrypt"), - format!("{key_manager_host}/data/decrypt"), - format!("{key_manager_host}/key/create"), - format!("{key_manager_host}/key/rotate"), - ]; - [&proxy_list, config_whitelist].concat().to_vec() -} - pub trait RequestBuilder: Send + Sync { fn json(&mut self, body: serde_json::Value); fn url_encoded_form(&mut self, body: serde_json::Value); @@ -201,6 +161,7 @@ where method: Method, url: String, ) -> CustomResult, ApiClientError>; + fn request_with_certificate( &self, method: Method, @@ -218,7 +179,9 @@ where ) -> CustomResult; fn add_request_id(&mut self, request_id: RequestId); + fn get_request_id(&self) -> Option; + fn add_flow_name(&mut self, flow_name: String); } @@ -226,60 +189,31 @@ dyn_clone::clone_trait_object!(ApiClient); #[derive(Clone)] pub struct ProxyClient { - proxy_client: reqwest::Client, - non_proxy_client: reqwest::Client, - whitelisted_urls: Vec, + proxy_config: Proxy, + client: reqwest::Client, request_id: Option, } impl ProxyClient { - pub fn new( - proxy_config: Proxy, - whitelisted_urls: Vec, - ) -> CustomResult { - let non_proxy_client = reqwest::Client::builder() - .redirect(reqwest::redirect::Policy::none()) - .build() - .change_context(ApiClientError::ClientConstructionFailed)?; - - let mut proxy_builder = - reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); - - if let Some(url) = proxy_config.https_url.as_ref() { - proxy_builder = proxy_builder.proxy( - reqwest::Proxy::https(url) - .change_context(ApiClientError::InvalidProxyConfiguration)?, - ); - } - - if let Some(url) = proxy_config.http_url.as_ref() { - proxy_builder = proxy_builder.proxy( - reqwest::Proxy::http(url) - .change_context(ApiClientError::InvalidProxyConfiguration)?, - ); - } - - let proxy_client = proxy_builder + pub fn new(proxy_config: &Proxy) -> CustomResult { + let client = get_client_builder(proxy_config)? .build() .change_context(ApiClientError::InvalidProxyConfiguration)?; Ok(Self { - proxy_client, - non_proxy_client, - whitelisted_urls, + proxy_config: proxy_config.clone(), + client, request_id: None, }) } pub fn get_reqwest_client( &self, - base_url: String, client_certificate: Option>, client_certificate_key: Option>, ) -> CustomResult { match (client_certificate, client_certificate_key) { (Some(certificate), Some(certificate_key)) => { - let client_builder = - reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()); + let client_builder = get_client_builder(&self.proxy_config)?; let identity = create_identity_from_certificate_and_key(certificate, certificate_key)?; Ok(client_builder @@ -290,13 +224,7 @@ impl ProxyClient { "Failed to construct client with certificate and certificate key", )?) } - (_, _) => { - if self.whitelisted_urls.contains(&base_url) { - Ok(self.non_proxy_client.clone()) - } else { - Ok(self.proxy_client.clone()) - } - } + (_, _) => Ok(self.client.clone()), } } } @@ -355,8 +283,6 @@ impl RequestBuilder for RouterRequestBuilder { } } -// TODO: remove this when integrating this trait -#[allow(dead_code)] #[async_trait::async_trait] impl ApiClient for ProxyClient { fn request( @@ -375,7 +301,7 @@ impl ApiClient for ProxyClient { certificate_key: Option>, ) -> CustomResult, ApiClientError> { let client_builder = self - .get_reqwest_client(url.clone(), certificate, certificate_key) + .get_reqwest_client(certificate, certificate_key) .change_context(ApiClientError::ClientConstructionFailed)?; Ok(Box::new(RouterRequestBuilder { inner: Some(client_builder.request(method, url)), diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs index 0ac8c419ef..da5f87a450 100644 --- a/crates/router/src/services/authentication/blacklist.rs +++ b/crates/router/src/services/authentication/blacklist.rs @@ -79,7 +79,7 @@ pub async fn check_user_in_blacklist( .get_key::>(token.as_str()) .await .change_context(ApiErrorResponse::InternalServerError) - .map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at)) + .map(|timestamp| timestamp > Some(token_issued_at)) } pub async fn check_role_in_blacklist( @@ -94,7 +94,7 @@ pub async fn check_role_in_blacklist( .get_key::>(token.as_str()) .await .change_context(ApiErrorResponse::InternalServerError) - .map(|timestamp| timestamp.map_or(false, |timestamp| timestamp > token_issued_at)) + .map(|timestamp| timestamp > Some(token_issued_at)) } #[cfg(feature = "email")] diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index da296373d8..db3483f816 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -33,7 +33,16 @@ where return Ok(role_info.clone()); } - let role_info = get_role_info_from_db(state, &token.role_id, &token.org_id).await?; + let role_info = get_role_info_from_db( + state, + &token.role_id, + &token.org_id, + token + .tenant_id + .as_ref() + .unwrap_or(&state.session_state().tenant.tenant_id), + ) + .await?; let token_expiry = i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?; @@ -68,6 +77,7 @@ async fn get_role_info_from_db( state: &A, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> RouterResult where A: SessionStateInfo + Sync, @@ -75,7 +85,7 @@ where state .session_state() .global_store - .find_by_role_id_and_org_id(role_id, org_id) + .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id) .await .map(roles::RoleInfo::from) .to_not_found_response(ApiErrorResponse::InvalidJwtToken) diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs index c9c64b7614..df2a14a1a1 100644 --- a/crates/router/src/services/authorization/roles.rs +++ b/crates/router/src/services/authorization/roles.rs @@ -121,29 +121,32 @@ impl RoleInfo { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) { Ok(role.clone()) } else { state .global_store - .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id) + .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id) .await .map(Self::from) } } - pub async fn from_role_id_and_org_id( + // TODO: To evaluate whether we can omit org_id and tenant_id for this function + pub async fn from_role_id_org_id_tenant_id( state: &SessionState, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> CustomResult { if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) { Ok(role.clone()) } else { state .global_store - .find_by_role_id_and_org_id(role_id, org_id) + .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id) .await .map(Self::from) } diff --git a/crates/router/src/services/conversion_impls.rs b/crates/router/src/services/conversion_impls.rs index 9bb88cf4ec..8572add041 100644 --- a/crates/router/src/services/conversion_impls.rs +++ b/crates/router/src/services/conversion_impls.rs @@ -1,3 +1,4 @@ +use common_utils::id_type; use error_stack::ResultExt; #[cfg(feature = "frm")] use hyperswitch_domain_models::router_data_v2::flow_common_types::FrmFlowData; @@ -23,17 +24,19 @@ fn get_irrelevant_id_string(id_name: &str, flow_name: &str) -> String { format!("irrelevant {id_name} in {flow_name} flow") } fn get_default_router_data( + tenant_id: id_type::TenantId, flow_name: &str, request: Req, response: Result, ) -> RouterData { RouterData { + tenant_id, flow: std::marker::PhantomData, - merchant_id: common_utils::id_type::MerchantId::get_irrelevant_merchant_id(), + merchant_id: id_type::MerchantId::get_irrelevant_merchant_id(), customer_id: None, connector_customer: None, connector: get_irrelevant_id_string("connector", flow_name), - payment_id: common_utils::id_type::PaymentId::get_irrelevant_id(flow_name) + payment_id: id_type::PaymentId::get_irrelevant_id(flow_name) .get_string_repr() .to_owned(), attempt_id: get_irrelevant_id_string("attempt_id", flow_name), @@ -93,6 +96,7 @@ impl RouterDataConversion for AccessTo let resource_common_data = Self {}; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -109,7 +113,12 @@ impl RouterDataConversion for AccessTo let Self {} = new_router_data.resource_common_data; let request = new_router_data.request.clone(); let response = new_router_data.response.clone(); - let router_data = get_default_router_data("access token", request, response); + let router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "access token", + request, + response, + ); Ok(router_data) } } @@ -153,6 +162,7 @@ impl RouterDataConversion for PaymentF }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -196,8 +206,12 @@ impl RouterDataConversion for PaymentF connector_response, payment_method_status, } = new_router_data.resource_common_data; - let mut router_data = - get_default_router_data("payment", new_router_data.request, new_router_data.response); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "payment", + new_router_data.request, + new_router_data.response, + ); router_data.merchant_id = merchant_id; router_data.customer_id = customer_id; router_data.connector_customer = connector_customer; @@ -256,6 +270,7 @@ impl RouterDataConversion for RefundFl }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -282,8 +297,12 @@ impl RouterDataConversion for RefundFl connector_request_reference_id, refund_id, } = new_router_data.resource_common_data; - let mut router_data = - get_default_router_data("refund", new_router_data.request, new_router_data.response); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "refund", + new_router_data.request, + new_router_data.response, + ); router_data.merchant_id = merchant_id; router_data.customer_id = customer_id; router_data.payment_id = payment_id; @@ -323,6 +342,7 @@ impl RouterDataConversion for Disputes }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -348,6 +368,7 @@ impl RouterDataConversion for Disputes dispute_id, } = new_router_data.resource_common_data; let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), "Disputes", new_router_data.request, new_router_data.response, @@ -386,6 +407,7 @@ impl RouterDataConversion for FrmFlowD minor_amount_captured: old_router_data.minor_amount_captured, }; Ok(RouterDataV2 { + tenant_id: old_router_data.tenant_id.clone(), flow: std::marker::PhantomData, resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), @@ -412,8 +434,12 @@ impl RouterDataConversion for FrmFlowD amount_captured, minor_amount_captured, } = new_router_data.resource_common_data; - let mut router_data = - get_default_router_data("frm", new_router_data.request, new_router_data.response); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "frm", + new_router_data.request, + new_router_data.response, + ); router_data.merchant_id = merchant_id; router_data.payment_id = payment_id; @@ -446,6 +472,7 @@ impl RouterDataConversion for FilesFlo }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -466,8 +493,12 @@ impl RouterDataConversion for FilesFlo connector_meta_data, connector_request_reference_id, } = new_router_data.resource_common_data; - let mut router_data = - get_default_router_data("files", new_router_data.request, new_router_data.response); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "files", + new_router_data.request, + new_router_data.response, + ); router_data.merchant_id = merchant_id; router_data.payment_id = payment_id; router_data.attempt_id = attempt_id; @@ -489,6 +520,7 @@ impl RouterDataConversion for WebhookS merchant_id: old_router_data.merchant_id.clone(), }; Ok(RouterDataV2 { + tenant_id: old_router_data.tenant_id.clone(), flow: std::marker::PhantomData, resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), @@ -505,6 +537,7 @@ impl RouterDataConversion for WebhookS { let Self { merchant_id } = new_router_data.resource_common_data; let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), "webhook source verify", new_router_data.request, new_router_data.response, @@ -532,6 +565,7 @@ impl RouterDataConversion for MandateR }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -551,6 +585,7 @@ impl RouterDataConversion for MandateR payment_id, } = new_router_data.resource_common_data; let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), "mandate revoke", new_router_data.request, new_router_data.response, @@ -559,7 +594,7 @@ impl RouterDataConversion for MandateR router_data.customer_id = Some(customer_id); router_data.payment_id = payment_id .unwrap_or_else(|| { - common_utils::id_type::PaymentId::get_irrelevant_id("mandate revoke") + id_type::PaymentId::get_irrelevant_id("mandate revoke") .get_string_repr() .to_owned() }) @@ -588,6 +623,7 @@ impl RouterDataConversion for PayoutFl quote_id: old_router_data.quote_id.clone(), }; Ok(RouterDataV2 { + tenant_id: old_router_data.tenant_id.clone(), flow: std::marker::PhantomData, resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), @@ -613,8 +649,12 @@ impl RouterDataConversion for PayoutFl payout_method_data, quote_id, } = new_router_data.resource_common_data; - let mut router_data = - get_default_router_data("payout", new_router_data.request, new_router_data.response); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "payout", + new_router_data.request, + new_router_data.response, + ); router_data.merchant_id = merchant_id; router_data.customer_id = customer_id; router_data.connector_customer = connector_customer; @@ -642,6 +682,7 @@ impl RouterDataConversion address: old_router_data.address.clone(), }; Ok(RouterDataV2 { + tenant_id: old_router_data.tenant_id.clone(), flow: std::marker::PhantomData, resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), @@ -662,6 +703,7 @@ impl RouterDataConversion address, } = new_router_data.resource_common_data; let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), "external authentication", new_router_data.request, new_router_data.response, @@ -692,6 +734,7 @@ impl RouterDataConversion for UasFlowD }; Ok(RouterDataV2 { flow: std::marker::PhantomData, + tenant_id: old_router_data.tenant_id.clone(), resource_common_data, connector_auth_type: old_router_data.connector_auth_type.clone(), request: old_router_data.request.clone(), @@ -709,8 +752,12 @@ impl RouterDataConversion for UasFlowD 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); + let mut router_data = get_default_router_data( + new_router_data.tenant_id.clone(), + "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/openidconnect.rs b/crates/router/src/services/openidconnect.rs index 4f5056f327..0c0f86431a 100644 --- a/crates/router/src/services/openidconnect.rs +++ b/crates/router/src/services/openidconnect.rs @@ -155,7 +155,7 @@ async fn get_oidc_reqwest_client( state: &SessionState, request: oidc::HttpRequest, ) -> Result { - let client = client::create_client(&state.conf.proxy, false, None, None) + let client = client::create_client(&state.conf.proxy, None, None) .map_err(|e| e.current_context().to_owned())?; let mut request_builder = client diff --git a/crates/router/src/services/pm_auth.rs b/crates/router/src/services/pm_auth.rs index d63dadc8b1..2d8097037d 100644 --- a/crates/router/src/services/pm_auth.rs +++ b/crates/router/src/services/pm_auth.rs @@ -11,9 +11,9 @@ use crate::{ services::{self}, }; -pub async fn execute_connector_processing_step<'b, 'a, T, Req, Resp>( +pub async fn execute_connector_processing_step<'b, T, Req, Resp>( state: &'b SessionState, - connector_integration: BoxedConnectorIntegration<'a, T, Req, Resp>, + connector_integration: BoxedConnectorIntegration<'_, T, Req, Resp>, req: &'b PaymentAuthRouterData, connector: &pm_auth_types::PaymentMethodAuthConnectors, ) -> errors::CustomResult, ConnectorError> diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index f9c9e6edb2..2ae2af9247 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -914,6 +914,7 @@ impl ForeignFrom<(&RouterData, T2) merchant_id: data.merchant_id.clone(), connector: data.connector.clone(), attempt_id: data.attempt_id.clone(), + tenant_id: data.tenant_id.clone(), status: data.status, payment_method: data.payment_method, connector_auth_type: data.connector_auth_type.clone(), @@ -983,6 +984,7 @@ impl merchant_id: data.merchant_id.clone(), connector: data.connector.clone(), attempt_id: data.attempt_id.clone(), + tenant_id: data.tenant_id.clone(), status: data.status, payment_method: data.payment_method, connector_auth_type: data.connector_auth_type.clone(), diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index b0c3d2d21d..243b6116e7 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -511,9 +511,9 @@ impl ConnectorData { enums::Connector::Worldpay => { Ok(ConnectorEnum::Old(Box::new(connector::Worldpay::new()))) } - // enums::Connector::Xendit => { - // Ok(ConnectorEnum::Old(Box::new(connector::Xendit::new()))) - // } + enums::Connector::Xendit => { + Ok(ConnectorEnum::Old(Box::new(connector::Xendit::new()))) + } enums::Connector::Mifinity => { Ok(ConnectorEnum::Old(Box::new(connector::Mifinity::new()))) } diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index ff72af4840..28533e33e1 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -305,13 +305,6 @@ 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, @@ -383,6 +376,6 @@ pub async fn create_profile_from_merchant_account( 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, + authentication_product_ids: request.authentication_product_ids, })) } diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 25227ae538..47ad15727e 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,16 +1,16 @@ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CardType, CustomerPaymentMethod, - CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, - GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, + CustomerPaymentMethodsListResponse, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, + GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, - PaymentMethodIntentConfirm, PaymentMethodIntentConfirmInternal, PaymentMethodIntentCreate, - PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodMigrate, PaymentMethodMigrateResponse, PaymentMethodResponse, - PaymentMethodResponseData, PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, - TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, - TokenizedWalletValue1, TokenizedWalletValue2, + PaymentMethodIntentConfirm, PaymentMethodIntentCreate, PaymentMethodListData, + PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, + PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodResponseData, + PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, TokenizePayloadEncrypted, + TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, + TokenizedWalletValue2, }; #[cfg(all( any(feature = "v2", feature = "v1"), diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 47d8add58c..4d248bbf0b 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -65,6 +65,7 @@ impl VerifyConnectorData { fn get_router_data( &self, + state: &SessionState, request_data: R1, access_token: Option, ) -> types::RouterData { @@ -81,6 +82,7 @@ impl VerifyConnectorData { attempt_id: attempt_id.clone(), description: None, customer_id: None, + tenant_id: state.tenant.tenant_id.clone(), merchant_id: common_utils::id_type::MerchantId::default(), reference_id: None, access_token, @@ -132,7 +134,7 @@ pub trait VerifyConnector { ) -> errors::RouterResponse<()> { let authorize_data = connector_data.get_payment_authorize_data(); let access_token = Self::get_access_token(state, connector_data.clone()).await?; - let router_data = connector_data.get_router_data(authorize_data, access_token); + let router_data = connector_data.get_router_data(state, authorize_data, access_token); let request = connector_data .connector diff --git a/crates/router/src/types/api/verify_connector/paypal.rs b/crates/router/src/types/api/verify_connector/paypal.rs index f7de86ceeb..d7c4fca748 100644 --- a/crates/router/src/types/api/verify_connector/paypal.rs +++ b/crates/router/src/types/api/verify_connector/paypal.rs @@ -17,7 +17,7 @@ impl VerifyConnector for connector::Paypal { ) -> errors::CustomResult, errors::ApiErrorResponse> { let token_data: types::AccessTokenRequestData = connector_data.connector_auth.clone().try_into()?; - let router_data = connector_data.get_router_data(token_data, None); + let router_data = connector_data.get_router_data(state, token_data, None); let request = connector_data .connector diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs index 070e583caa..9a87b6d28b 100644 --- a/crates/router/src/types/domain.rs +++ b/crates/router/src/types/domain.rs @@ -16,6 +16,10 @@ mod customers { pub use hyperswitch_domain_models::customer::*; } +mod callback_mapper { + pub use hyperswitch_domain_models::callback_mapper::CallbackMapper; +} + pub use customers::*; pub use merchant_account::*; @@ -39,6 +43,7 @@ pub mod user_key_store; pub use address::*; pub use business_profile::*; +pub use callback_mapper::*; pub use consts::*; pub use event::*; pub use merchant_connector_account::*; diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs index d4cd9ef62d..bb12365906 100644 --- a/crates/router/src/types/domain/types.rs +++ b/crates/router/src/types/domain/types.rs @@ -7,6 +7,8 @@ impl From<&crate::SessionState> for KeyManagerState { fn from(state: &crate::SessionState) -> Self { let conf = state.conf.key_manager.get_inner(); Self { + global_tenant_id: state.conf.multitenancy.global_tenant.tenant_id.clone(), + tenant_id: state.tenant.tenant_id.clone(), enabled: conf.enabled, url: conf.url.clone(), client_idle_timeout: state.conf.proxy.idle_pool_connection_timeout, diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index 24573548d7..96a0d58005 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -6,6 +6,7 @@ pub mod blocklist; pub mod blocklist_fingerprint; pub mod blocklist_lookup; pub mod business_profile; +pub mod callback_mapper; pub mod capture; pub mod cards_info; pub mod configs; @@ -63,13 +64,13 @@ 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::*, 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::*, + blocklist_fingerprint::*, blocklist_lookup::*, business_profile::*, callback_mapper::*, + capture::*, cards_info::*, 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/callback_mapper.rs b/crates/router/src/types/storage/callback_mapper.rs new file mode 100644 index 0000000000..4f66a56c6f --- /dev/null +++ b/crates/router/src/types/storage/callback_mapper.rs @@ -0,0 +1 @@ +pub use diesel_models::callback_mapper::CallbackMapper; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index bbcdfc535d..06682f596e 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -313,7 +313,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { 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::Xendit => Self::Xendit, api_enums::Connector::Zen => Self::Zen, api_enums::Connector::Zsl => Self::Zsl, #[cfg(feature = "dummy_connector")] @@ -662,6 +662,22 @@ impl ForeignTryFrom for storage_enum } } +impl ForeignTryFrom for api_enums::RelayStatus { + type Error = errors::ValidationError; + + fn foreign_try_from( + value: api_models::webhooks::IncomingWebhookEvent, + ) -> Result { + match value { + api_models::webhooks::IncomingWebhookEvent::RefundSuccess => Ok(Self::Success), + api_models::webhooks::IncomingWebhookEvent::RefundFailure => Ok(Self::Failure), + _ => Err(errors::ValidationError::IncorrectValueProvided { + field_name: "incoming_webhook_event_type", + }), + } + } +} + #[cfg(feature = "payouts")] impl ForeignTryFrom for storage_enums::PayoutStatus { type Error = errors::ValidationError; diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index e4046f95a1..f9ef488092 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -55,9 +55,10 @@ use crate::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, payments as payments_core, }, + headers::ACCEPT_LANGUAGE, logger, routes::{metrics, SessionState}, - services, + services::{self, authentication::get_header_value_by_key}, types::{ self, domain, transformers::{ForeignFrom, ForeignInto}, @@ -1324,3 +1325,11 @@ pub async fn trigger_refund_outgoing_webhook( ) -> RouterResult<()> { todo!() } + +pub fn get_locale_from_header(headers: &actix_web::http::header::HeaderMap) -> String { + get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers) + .ok() + .flatten() + .map(|val| val.to_string()) + .unwrap_or(common_utils::consts::DEFAULT_LOCALE.to_string()) +} diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index b8ffcf836d..ef06531b42 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -77,9 +77,14 @@ impl UserFromToken { } pub async fn get_role_info_from_db(&self, state: &SessionState) -> UserResult { - RoleInfo::from_role_id_and_org_id(state, &self.role_id, &self.org_id) - .await - .change_context(UserErrors::InternalServerError) + RoleInfo::from_role_id_org_id_tenant_id( + state, + &self.role_id, + &self.org_id, + self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), + ) + .await + .change_context(UserErrors::InternalServerError) } } diff --git a/crates/router/src/utils/user/dashboard_metadata.rs b/crates/router/src/utils/user/dashboard_metadata.rs index 39a76844be..b5e16290fc 100644 --- a/crates/router/src/utils/user/dashboard_metadata.rs +++ b/crates/router/src/utils/user/dashboard_metadata.rs @@ -281,7 +281,7 @@ pub fn parse_string_to_enums(query: String) -> UserResult, value_to_be_checked: &str) -> bool { value .as_ref() - .map_or(false, |mail| !mail.contains(value_to_be_checked)) + .is_some_and(|mail| !mail.contains(value_to_be_checked)) } pub fn is_prod_email_required(data: &ProdIntent, user_email: String) -> bool { diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index ac8ee11fc6..7413e66070 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -48,6 +48,7 @@ pub async fn validate_role_name( role_name: &domain::RoleName, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> UserResult<()> { let role_name_str = role_name.clone().get_role_name(); @@ -58,7 +59,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 .global_store - .list_all_roles(merchant_id, org_id) + .list_all_roles(merchant_id, org_id, tenant_id) .await .change_context(UserErrors::InternalServerError)? .iter() @@ -78,18 +79,24 @@ pub async fn set_role_info_in_cache_by_user_role( let Some(ref org_id) = user_role.org_id else { return false; }; - 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() + set_role_info_in_cache_if_required( + state, + user_role.role_id.as_str(), + org_id, + &user_role.tenant_id, + ) + .await + .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e)) + .is_ok() } pub async fn set_role_info_in_cache_by_role_id_org_id( state: &SessionState, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> bool { - set_role_info_in_cache_if_required(state, role_id, org_id) + set_role_info_in_cache_if_required(state, role_id, org_id, tenant_id) .await .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e)) .is_ok() @@ -99,15 +106,17 @@ pub async fn set_role_info_in_cache_if_required( state: &SessionState, role_id: &str, org_id: &id_type::OrganizationId, + tenant_id: &id_type::TenantId, ) -> UserResult<()> { if roles::predefined_roles::PREDEFINED_ROLES.contains_key(role_id) { return Ok(()); } - 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")?; + let role_info = + roles::RoleInfo::from_role_id_org_id_tenant_id(state, role_id, org_id, tenant_id) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Error getting role_info from role_id")?; authz::set_role_info_in_cache( state, diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 55b92b4aac..a1f85534b6 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -20,6 +20,7 @@ async fn invalidate_existing_cache_success() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 18e9c43ed6..8b3372d7de 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -27,6 +27,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { flow: PhantomData, merchant_id, customer_id: Some(id_type::CustomerId::try_from(Cow::from("aci")).unwrap()), + tenant_id: id_type::TenantId::try_from_string("public".to_string()).unwrap(), connector: "aci".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -145,6 +146,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { flow: PhantomData, merchant_id, customer_id: Some(id_type::CustomerId::try_from(Cow::from("aci")).unwrap()), + tenant_id: id_type::TenantId::try_from_string("public".to_string()).unwrap(), connector: "aci".to_string(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), @@ -220,6 +222,7 @@ async fn payments_create_success() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -270,6 +273,7 @@ async fn payments_create_failure() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -336,6 +340,7 @@ async fn refund_for_successful_payments() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -409,6 +414,7 @@ async fn refunds_create_failure() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 305b11fe5b..2cf3dc965f 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -490,6 +490,8 @@ pub trait ConnectorActions: Connector { merchant_id, customer_id: Some(common_utils::generate_customer_id_of_default_length()), connector: self.get_name(), + tenant_id: common_utils::id_type::TenantId::try_from_string("public".to_string()) + .unwrap(), payment_id: uuid::Uuid::new_v4().to_string(), attempt_id: uuid::Uuid::new_v4().to_string(), status: enums::AttemptStatus::default(), @@ -602,6 +604,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -645,6 +648,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -689,6 +693,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -732,6 +737,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -826,6 +832,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -866,6 +873,7 @@ async fn call_connector< let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index beaacb79fc..e1fe40b420 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -297,6 +297,7 @@ async fn payments_create_core() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -558,6 +559,7 @@ async fn payments_create_core_adyen_no_redirect() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 1d573d007b..49d2e12b81 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -58,6 +58,7 @@ async fn payments_create_core() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -327,6 +328,7 @@ async fn payments_create_core_adyen_no_redirect() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/services.rs b/crates/router/tests/services.rs index c014370b24..36f969dac1 100644 --- a/crates/router/tests/services.rs +++ b/crates/router/tests/services.rs @@ -20,6 +20,7 @@ async fn get_redis_conn_failure() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -51,6 +52,7 @@ async fn get_redis_conn_success() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router_env/src/logger/config.rs b/crates/router_env/src/logger/config.rs index 431fb5a636..a7bdbe0696 100644 --- a/crates/router_env/src/logger/config.rs +++ b/crates/router_env/src/logger/config.rs @@ -175,18 +175,29 @@ impl Config { if let Some(explicit_config_path_val) = explicit_config_path { config_path.push(explicit_config_path_val); } else { - let config_directory = - std::env::var(crate::env::vars::CONFIG_DIR).unwrap_or_else(|_| "config".into()); let config_file_name = match environment { "production" => "production.toml", "sandbox" => "sandbox.toml", _ => "development.toml", }; - config_path.push(crate::env::workspace_path()); + let config_directory = Self::get_config_directory(); config_path.push(config_directory); config_path.push(config_file_name); } config_path } + + /// Get the Directory for the config file + /// Read the env variable `CONFIG_DIR` or fallback to `config` + pub fn get_config_directory() -> PathBuf { + let mut config_path = PathBuf::new(); + + let config_directory = + std::env::var(crate::env::vars::CONFIG_DIR).unwrap_or_else(|_| "config".into()); + + config_path.push(crate::env::workspace_path()); + config_path.push(config_directory); + config_path + } } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 10183f75d5..935efa3c78 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -537,6 +537,8 @@ pub enum Flow { Relay, /// Relay retrieve flow RelayRetrieve, + /// Incoming Relay Webhook Receive + IncomingRelayWebhookReceive, } /// Trait for providing generic behaviour to flow metric diff --git a/crates/storage_impl/src/callback_mapper.rs b/crates/storage_impl/src/callback_mapper.rs new file mode 100644 index 0000000000..186f2b7f92 --- /dev/null +++ b/crates/storage_impl/src/callback_mapper.rs @@ -0,0 +1,28 @@ +use diesel_models::callback_mapper::CallbackMapper as DieselCallbackMapper; +use hyperswitch_domain_models::callback_mapper::CallbackMapper; + +use crate::DataModelExt; + +impl DataModelExt for CallbackMapper { + type StorageModel = DieselCallbackMapper; + + fn to_storage_model(self) -> Self::StorageModel { + DieselCallbackMapper { + id: self.id, + type_: self.type_, + data: self.data, + created_at: self.created_at, + last_modified_at: self.last_modified_at, + } + } + + fn from_storage_model(storage_model: Self::StorageModel) -> Self { + Self { + id: storage_model.id, + type_: storage_model.type_, + data: storage_model.data, + created_at: storage_model.created_at, + last_modified_at: storage_model.last_modified_at, + } + } +} diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index 09bb42567d..e0722ef52e 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -6,6 +6,7 @@ use hyperswitch_domain_models::errors::{StorageError, StorageResult}; use masking::StrongSecret; use redis::{kv_store::RedisConnInterface, pub_sub::PubSubInterface, RedisStore}; mod address; +pub mod callback_mapper; pub mod config; pub mod connection; pub mod customers; diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 9203a14ae2..17974a3b9b 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -288,10 +288,10 @@ impl std::fmt::Display for Op<'_> { } } -pub async fn decide_storage_scheme<'a, T, D>( +pub async fn decide_storage_scheme( store: &KVRouterStore, storage_scheme: MerchantStorageScheme, - operation: Op<'a>, + operation: Op<'_>, ) -> MerchantStorageScheme where D: de::DeserializeOwned diff --git a/cypress-tests-v2/package-lock.json b/cypress-tests-v2/package-lock.json index cac88cab05..644b9c9fae 100644 --- a/cypress-tests-v2/package-lock.json +++ b/cypress-tests-v2/package-lock.json @@ -29,9 +29,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", - "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz", + "integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -48,7 +48,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.13.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", @@ -79,6 +79,128 @@ "ms": "^2.1.1" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -101,13 +223,13 @@ } }, "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -118,9 +240,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" }, @@ -460,18 +582,29 @@ "node": ">=6" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -568,9 +701,9 @@ } }, "node_modules/ci-info": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", - "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "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": [ { @@ -729,7 +862,7 @@ "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -742,9 +875,9 @@ } }, "node_modules/cypress": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", - "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", + "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", @@ -882,9 +1015,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { @@ -913,24 +1046,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -951,6 +1066,29 @@ "node": ">=0.3.1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -994,14 +1132,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -1016,6 +1151,19 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1057,7 +1205,7 @@ "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", + "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", @@ -1194,6 +1342,38 @@ "flat": "cli.js" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1286,17 +1466,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -1342,22 +1527,22 @@ } }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=12" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1377,6 +1562,23 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -1394,13 +1596,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1423,36 +1625,10 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -1716,6 +1892,23 @@ "dev": true, "license": "MIT" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1973,6 +2166,24 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2037,10 +2248,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.0.1.tgz", + "integrity": "sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==", "dev": true, "license": "MIT", "peer": true, @@ -2052,7 +2274,7 @@ "diff": "^5.2.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", @@ -2071,7 +2293,7 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/mocha/node_modules/escape-string-regexp": { @@ -2447,9 +2669,9 @@ "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==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", "dev": true, "funding": [ { @@ -2500,9 +2722,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": { @@ -2615,6 +2837,14 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2645,6 +2875,24 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -2684,9 +2932,9 @@ } }, "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": { @@ -2753,9 +3001,9 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2917,24 +3165,6 @@ "dev": true, "license": "ISC" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2959,16 +3189,73 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3040,6 +3327,23 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3053,6 +3357,21 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -3128,22 +3447,22 @@ "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==", + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", + "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.59" + "tldts-core": "^6.1.69" }, "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==", + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", + "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", "dev": true, "license": "MIT" }, @@ -3195,9 +3514,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "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" }, @@ -3235,9 +3554,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "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" }, @@ -3345,6 +3664,26 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/cypress-tests/.gitignore b/cypress-tests/.gitignore index 663fbaa3f4..fc03eb39cb 100644 --- a/cypress-tests/.gitignore +++ b/cypress-tests/.gitignore @@ -1,5 +1,10 @@ +# Mac OS files .DS_Store + +# Output files +reports/ +screenshots/ +videos/ + creds.json -reports run_all.sh -screenshots diff --git a/cypress-tests/README.md b/cypress-tests/README.md index 0a071aa34a..87681bb6ea 100644 --- a/cypress-tests/README.md +++ b/cypress-tests/README.md @@ -1,31 +1,82 @@ -# Cypress Tests +# Hyperswitch Cypress Testing Framework ## Overview -This Tool is a solution designed to automate testing for the [Hyperswitch](https://github.com/juspay/hyperswitch/) using Cypress, an open-source tool capable of conducting API call tests and UI tests. This README provides guidance on installing Cypress and its dependencies. - -## Installation - -### Prerequisites - -Before installing Cypress, ensure that `Node` and `npm` is installed on your machine. To check if it is installed, run the following command: +This is a comprehensive testing framework built with [Cypress](https://cypress.io) to automate testing for [Hyperswitch](https://github.com/juspay/hyperswitch/). The framework supports API testing with features like multiple credential management, configuration management, global state handling, and extensive utility functions. The framework provides extensive support for API testing with advanced features including: + +- [Multiple credential management](#multiple-credential-support) +- [Dynamic configuration management](#dynamic-configuration-management) +- Global state handling +- Extensive utility functions +- Parallel test execution +- Connector-specific implementations + +## Table of Contents + +- [Overview](#overview) +- [Table of Contents](#table-of-contents) +- [Quick Start](#quick-start) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Running Tests](#running-tests) + - [Development Mode (Interactive)](#development-mode-interactive) + - [CI Mode (Headless)](#ci-mode-headless) + - [Execute tests against multiple connectors or in parallel](#execute-tests-against-multiple-connectors-or-in-parallel) +- [Test reports](#test-reports) +- [Folder structure](#folder-structure) +- [Adding tests](#adding-tests) + - [Addition of test for a new connector](#addition-of-test-for-a-new-connector) + - [Developing Core Features or adding new tests](#developing-core-features-or-adding-new-tests) + - [1. Create or update test file](#1-create-or-update-test-file) + - [2. Add New Commands](#2-add-new-commands) + - [Managing global state](#managing-global-state) +- [Debugging](#debugging) + - [1. Interactive Mode](#1-interactive-mode) + - [2. Logging](#2-logging) + - [3. Screenshots](#3-screenshots) + - [4. State Debugging](#4-state-debugging) + - [5. Hooks](#5-hooks) + - [6. Tasks](#6-tasks) +- [Linting](#linting) +- [Best Practices](#best-practices) +- [Additional Resources](#additional-resources) +- [Contributing](#contributing) +- [Appendix](#appendix) + - [Example creds.json](#example-credsjson) + - [Multiple credential support](#multiple-credential-support) + - [Dynamic configuration management](#dynamic-configuration-management) + +## Quick Start + +For experienced users who want to get started quickly: ```shell -node -v -npm -v +git clone https://github.com/juspay/hyperswitch.git +cd hyperswitch/cypress-tests +npm ci +# connector_id must be replaced with the connector name that is being tested (e.g. stripe, paypal, etc.) +CYPRESS_CONNECTOR="connector_id" npm run cypress:ci ``` -If not, download and install `Node` from the official [Node.js website](https://nodejs.org/en/download/package-manager/current). This will also install `npm`. +## Getting Started -### Run Test Cases on your local +## Prerequisites -To run test cases, follow these steps: +- Node.js (18.x or above) +- npm or yarn +- [Hyperswitch development environment](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md) + +> [!NOTE] +> To learn about the hardware requirements and software dependencies for running Cypress, refer to the [official documentation](https://docs.cypress.io/app/get-started/install-cypress). + +## Installation 1. Clone the repository and switch to the project directory: ```shell - git clone https://github.com/juspay/hyperswitch - cd cypress-tests + git clone https://github.com/juspay/hyperswitch.git + cd hyperswitch/cypress-tests ``` 2. Install Cypress and its dependencies to `cypress-tests` directory by running the following command: @@ -34,65 +85,70 @@ To run test cases, follow these steps: npm ci ``` -3. Insert data to `cards_info` table in `hyperswitch_db` + Once installed, verify the installation by running: ```shell - psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" + npx cypress --version ``` -4. Set environment variables for cypress + To learn about the supported commands, execute: ```shell - export CYPRESS_CONNECTOR="connector_id" - export CYPRESS_BASEURL="base_url" - export DEBUG=cypress:cli - export CYPRESS_ADMINAPIKEY="admin_api_key" - export CYPRESS_CONNECTOR_AUTH_FILE_PATH="path/to/creds.json" + npm run ``` -5. Run Cypress test cases - - To run the tests in interactive mode run the following command +3. Set up the cards database: ```shell - npm run cypress + psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" ``` - To run all the tests in headless mode run the following command +4. Set environment variables for cypress ```shell - npm run cypress:ci + export CYPRESS_CONNECTOR="connector_id" + export CYPRESS_BASEURL="base_url" + export DEBUG=cypress:cli + export CYPRESS_ADMINAPIKEY="admin_api_key" + export CYPRESS_CONNECTOR_AUTH_FILE_PATH="path/to/creds.json" ``` - To run payment tests in headless mode run the following command +> [!TIP] +> It is recommended to install [direnv](https://github.com/direnv/direnv) and use a `.envrc` file to store these environment variables with `cypress-tests` directory. This will make it easier to manage environment variables while working with Cypress tests. - ```shell - npm run cypress:payments - ``` +> [!NOTE] +> To learn about how `creds` file should be structured, refer to the [example.creds.json](#example-credsjson) section below. - To run payout tests in headless mode run the following command +## Running Tests - ```shell - npm run cypress:payouts - ``` +Execution of Cypress tests can be done in two modes: Development mode (Interactive) and CI mode (Headless). The tests can be executed against a single connector or multiple connectors in parallel. Time taken to execute the tests will vary based on the number of connectors and the number of tests. For a single connector, the tests will take approximately 07-12 minutes to execute (this also depends on the hardware configurations). - To run routing tests in headless mode run the following command +For Development mode, the tests will run in the Cypress UI where execution of tests can be seen in real-time and provides a larger area for debugging based on the need. In CI mode (Headless), tests run in the terminal without UI interaction and generate reports automatically. - ```shell - npm run cypress:routing - ``` +### Development Mode (Interactive) -In order to run cypress tests against multiple connectors at a time or in parallel: +```shell +npm run cypress +``` -1. Set up `.env` file that exports necessary info: +### CI Mode (Headless) - ```env - export DEBUG=cypress:cli +```shell +# All tests +npm run cypress:ci + +# Specific test suites +npm run cypress:payments # Payment tests +npm run cypress:payment-method-list # Payment method list tests +npm run cypress:payouts # Payout tests +npm run cypress:routing # Routing tests +``` - export CYPRESS_ADMINAPIKEY='admin_api_key' - export CYPRESS_BASEURL='base_url' - export CYPRESS_CONNECTOR_AUTH_FILE_PATH="path/to/creds.json" +### Execute tests against multiple connectors or in parallel +1. Set additional environment variables: + + ```shell export PAYMENTS_CONNECTORS="payment_connector_1 payment_connector_2 payment_connector_3 payment_connector_4" export PAYOUTS_CONNECTORS="payout_connector_1 payout_connector_2 payout_connector_3" export PAYMENT_METHOD_LIST="" @@ -103,72 +159,64 @@ In order to run cypress tests against multiple connectors at a time or in parall ```shell source .env - scripts/execute_cypress.sh + ../scripts/execute_cypress.sh ``` Optionally, `--parallel ` can be passed to run cypress tests in parallel. By default, when `parallel` command is passed, it will be run in batches of `5`. -> [!NOTE] -> To learn about how creds file should be structured, refer to the [example.creds.json](#example-credsjson) section below. - -## Folder Structure +## Test reports -The folder structure of this directory is as follows: +The test reports are generated in the `cypress/reports` directory. The reports are generated in the `mochawesome` format and can be viewed in the browser. +These reports does include: -```text -. # The root directory for the Cypress tests. -├── .gitignore -├── cypress # Contains Cypress-related files and folders. -│ ├── e2e # End-to-end test directory. -│ │ ├── ConnectorTest # Directory for test scenarios related to connectors. -│ │ │ ├── your_testcase1_files_here.cy.js -│ │ │ ├── your_testcase2_files_here.cy.js -│ │ │ └── ... -│ │ └── ConnectorUtils # Directory for utility functions related to connectors. -│ │ ├── connector_detail_files_here.js -│ │ └── utils.js -│ ├── fixtures # Directory for storing test data API request. -│ │ └── your_fixture_files_here.json -│ ├── support # Directory for Cypress support files. -│ │ ├── commands.js # File containing custom Cypress commands and utilities. -│ │ └── e2e.js -│ └── utils -│ └── utility_files_go_here.js -├── cypress.config.js # Cypress configuration file. -├── cypress.env.json # File is used to store environment-specific configuration values,such as base URLs, which can be accessed within your Cypress tests. -├── package.json # Node.js package file. -├── readme.md # This file -└── yarn.lock -``` +- screenshots of the failed tests +- HTML and JSON reports -## Writing Tests +## Folder structure -### Adding Connectors +The folder structure of this directory is as follows: -To add a new connector for testing with Hyperswitch, follow these steps: +```txt +. +├── .prettierrc # prettier configs +├── README.md # this file +├── cypress +│   ├── e2e +│   │   ├── Test # Directory for test scenarios related to connectors. +│   │   │   ├── 00000-test_<0>.cy.js +│   │   │   ├── ... +│   │   │   └── 0000n-test_.cy.js +│   │   └── Utils # Directory for utility functions related to connectors. +│   │   ├── connector_<1>.js +│   │   ├── ... +│   │   └── connector_.js +│   ├── fixtures # Directory for storing test data API request. +│   │   ├── fixture_<1>.json +│   │   ├── ... +│   │   └── fixture_.json +│   ├── support # Directory for Cypress support files. +│   │   ├── commands.js # File containing custom Cypress commands and utilities. +│   │   ├── e2e.js +│   │   └── redirectionHandler.js +│   └── utils +│   ├── RequestBodyUtils.js +│   ├── State.js +│   └── featureFlags.js +├── cypress.config.js # Cypress configuration file. +├── eslint.config.js # linter configuration file. +└── package.json # Node.js package file. +``` -1. Include the connector details in the `creds.json` file: +## Adding tests - example: +### Addition of test for a new connector - ```json - { - "stripe": { - "connector_account_details": { - "auth_type": "HeaderKey", - "api_key": "SK_134" - } - } - } - ``` +1. Include the connector details in the `creds.json` file 2. Add the new connector details to the ConnectorUtils folder (including CardNo and connector-specific information). - Refer to Stripe.js file for guidance: - - ```javascript - /cypress-tests/cypress/e2e/ConnectorUtils/Stripe.js - ``` + To add a new Payment connector, refer to [`Stripe.js`](cypress/e2e/PaymentUtils/Stripe.js) file for reference. + To add a new Payout connector, refer to [`Adyen.js`](cypress-tests/cypress/e2e/PayoutUtils/Adyen.js) file for reference. **File Naming:** Create a new file named .js for your specific connector. @@ -176,107 +224,173 @@ To add a new connector for testing with Hyperswitch, follow these steps: **Handling Unsupported Features:** - - If a connector does not support a specific payment method or feature: - - You can omit the relevant configurations in the .js file. - - The handling of unsupported features will be managed by the commons.js file, which will throw an unsupported or not implemented error as appropriate. + - If a connector does not support a specific payment method or a feature: + - The relevant configurations in the `.js` file can be omitted + - The handling of unsupported or unimplemented features will be managed by the [`Commons.js`](cypress/e2e/PaymentUtils/Commons.js) file, which will throw the appropriate `unsupported` or `not implemented` error + +3. In `Utils.js`, import the new connector details + +4. If the connector has a specific redirection requirement, add relevant redirection logic in `support/redirectionHandler.js` -3. In `Utils.js`, import the new connector details. +### Developing Core Features or adding new tests + +#### 1. Create or update test file + +To add a new test, create a new test file in the `e2e` directory under respective `service`. The test file should follow the naming convention `000-Test.cy.js` and should contain the test cases related to the service. + +```javascript +// cypress/e2e/Test/NewFeature.cy.js +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; -### Adding Functions +describe("New Feature", () => { + let globalState; -Similarly, add any helper functions or utilities in the `commands.js` in support folder and import them into your tests as needed. + before(() => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("tests new functionality", () => { + // Test implementation + }); +}); +``` -Example: Adding List Mandate function to support `ListMandate` scenario +#### 2. Add New Commands ```javascript -Cypress.Commands.add("listMandateCallTest", (globalState) => { - // declare all the variables and constants - const customerId = globalState.get("customerId"); - // construct the URL for the API call - const url: `${globalState.get("baseUrl")}/customers/${customerId}/mandates` - const api_key = globalState.get("apiKey"); +// cypress/support/commands.js +Cypress.Commands.add("newCommand", (params, globalState) => { + const baseUrl = globalState.get("baseUrl"); + const apiKey = globalState.get("apiKey"); + const url = `${baseUrl}/endpoint`; cy.request({ - method: "GET", + method: "POST", url: url, headers: { - "Content-Type": "application/json", - "api-key": api_key, + "api-key": apiKey, }, - // set failOnStatusCode to false to prevent Cypress from failing the test - failOnStatusCode: false, + body: params, }).then((response) => { - // mandatorliy log the `x-request-id` to the console - logRequestId(response.headers["x-request-id"]); - - expect(response.headers["content-type"]).to.include("application/json"); - - if (response.status === 200) { - // do the necessary validations like below - for (const key in response.body) { - expect(response.body[key]).to.have.property("mandate_id"); - expect(response.body[key]).to.have.property("status"); - } - } else { - // handle the error response - expect(response.status).to.equal(400); - } + // Assertions }); }); ``` -### Adding Scenarios +### Managing global state -To add new test scenarios: +The global state is used to share data between tests. The global state is stored in the `State` class and is accessible across all tests. Can only be accessed in the `before` and `after` hooks. -1. Navigate to the ConnectorTest directory. -2. Create a new test file or modify existing ones to add your scenarios. -3. Write your test scenarios using Cypress commands. +## Debugging -For example, to add a scenario for listing mandates in the `Mandateflows`: +### 1. Interactive Mode + +- Use `npm run cypress` for real-time test execution +- View request/response details in Cypress UI +- Use DevTools for deeper debugging + +### 2. Logging ```javascript -// cypress/ConnectorTest/CreateSingleuseMandate.js -describe("Payment Scenarios", () => { - it("should complete a successful payment", () => { - // Your test logic here - }); -}); +cy.task("cli_log", "Debug message"); +cy.log("Test state:", globalState.data); ``` -In this scenario, you can call functions defined in `command.js`. For instance, to test the `listMandateCallTest` function: +### 3. Screenshots + +- Automatically captured on test failure +- Custom screenshot capture: ```javascript -describe("Payment Scenarios", () => { - it("list-mandate-call-test", () => { - cy.listMandateCallTest(globalState); - }); +cy.screenshot("debug-state"); +``` + +### 4. State Debugging + +- Add state logging in hooks: + +```javascript +beforeEach(() => { + cy.log("Current state:", JSON.stringify(globalState.data)); }); ``` -You can create similar scenarios by calling other functions defined in `commands.js`. These functions interact with utility files like `.js` and include necessary assertions to support various connector scenarios. +### 5. Hooks -### Debugging +- If the `globalState` object does not contain latest data, it must be due to the hooks not being executed in the correct order +- Add `cy.log(globalState)` to the test case to verify the data in the `globalState` object -It is recommended to run `npm run cypress` while developing new test cases to debug and verify as it opens the Cypress UI allowing the developer to run individual tests. This also opens up the possibility to to view the test execution in real-time and debug any issues that may arise by viewing the request and response payloads directly. +> [!NOTE] +> Refer to the Cypress's official documentation for more information on hooks and their execution order [here](https://docs.cypress.io/app/core-concepts/writing-and-organizing-tests#Hooks). + +### 6. Tasks + +- Use `cy.task` to interact with the Node.js environment +- Task can only be used in `support` files and `spec` files. Using them in files outside these directories will result in unexpected behavior or errors like abrupt termination of the test suite + +## Linting -If, for any reason, the `globalState` object does not contain latest data, it must be due to the hooks not being executed in the correct order. In such cases, it is recommended to add `cy.log(globalState)` to the test case to verify the data in the `globalState` object. -Please refer to the Cypress's official documentation for more information on hooks and their execution order [here](https://docs.cypress.io/app/core-concepts/writing-and-organizing-tests#Hooks). +To run the formatting and lint checks, execute the following command: + +```shell +# Format the code +npm run format + +# Check the formatting +npm run format:check + +# Lint the code. This wont fix the logic issues, unused imports or variables +npm run lint -- --fix +``` + +## Best Practices + +1. Use the global state for sharing data between tests +2. Implement proper error handling +3. Use appropriate wait strategies +4. Maintain test independence +5. Follow the existing folder structure +6. Document connector-specific behaviors +7. Use descriptive test and variable names +8. Use custom commands for repetitive tasks +9. Use `cy.log` for debugging and do not use `console.log` ## Additional Resources -For more information on using Cypress and writing effective tests, refer to the official Cypress documentation: [Cypress Documentation](https://docs.cypress.io/) +- [Cypress Documentation](https://docs.cypress.io/) +- [API Testing Best Practices](https://docs.cypress.io/guides/end-to-end-testing/api-testing) +- [Hyperswitch API Documentation](https://hyperswitch.io/docs) + +## Contributing -## Example creds.json +1. Fork the repository +2. Create a feature branch +3. Add tests following the guidelines +4. Submit a pull request + +## Appendix + +### Example creds.json ```json { + // Connector with single credential support and metadata support "adyen": { "connector_account_details": { "auth_type": "SignatureKey", "api_key": "api_key", "key1": "key1", "api_secret": "api_secret" + }, + "metadata": { + "key": "value" } }, "bankofamerica": { @@ -294,12 +408,23 @@ For more information on using Cypress and writing effective tests, refer to the "key1": "key1" } }, + // Connector with multiple credential support "cybersource": { - "connector_account_details": { - "auth_type": "SignatureKey", - "api_key": "api_key", - "key1": "key1", - "api_secret": "api_secret" + "connector_1": { + "connector_account_details": { + "auth_type": "SignatureKey", + "api_key": "api_key", + "key1": "key1", + "api_secret": "api_secret" + } + }, + "connector_2": { + "connector_account_details": { + "auth_type": "SignatureKey", + "api_key": "api_key", + "key1": "key1", + "api_secret": "api_secret" + } } }, "nmi": { @@ -332,3 +457,54 @@ For more information on using Cypress and writing effective tests, refer to the } } ``` + +### Multiple credential support + +- There are some use cases where a connector supports a feature that requires a different set of API keys (example: Network transaction ID for Stripe expects a different API Key to be passed). This forces the need for having multiple credentials that serves different use cases +- This basically means that a connector can have multiple credentials +- At present the maximum number of credentials that can be supported is `2` +- The `creds.json` file should be structured to support multiple credentials for such connectors. The `creds.json` file should be structured as follows: + +```json +{ + "connector_name": { + "connector_1": { + "connector_account_details": { + "auth_type": "SignatureKey", + "api_key": "api_key", + "key1": "key1", + "api_secret": "api_secret" + } + }, + "connector_2": { + "connector_account_details": { + "auth_type": "SignatureKey", + "api_key": "api_key", + "key1": "key1", + "api_secret": "api_secret" + } + } + } +} +``` + +### Dynamic configuration management + +- `Configs` is the new `object` that is introduced to manage the dynamic configurations that are required for the tests +- This is supposed to be passed in an exchange (configuration for a specific can be passed to a test based on the need and this will impact everywhere in the test execution for that connector) +- At present, only 3 configs are supported: + - `DELAY`: This is used to introduce a delay in the test execution. This is useful when a connector requires a delay in order to perform a specific operation + - `CONNECTOR_CREDENTIAL`: This is used to control the connector credentials that are used in the test execution. This is useful only when a connector supports multiple credentials and the test needs to be executed with a specific credential + - `TRIGGER_SKIP`: This is used to skip a test execution (preferably redirection flows). This is useful when a test is does not support a specific redirection flow and needs to be skipped +- Example: In order to refund a payment in Trustpay, a `DELAY` of at least `5` seconds is required. By passing `DELAY` to the `Configs` object for Trustpay, the delay will be applied to all the tests that are executed for Trustpay + +```json +{ + "Configs": { + "DELAY": { + "STATUS": true, + "TIMEOUT": 15000 + } + } +} +``` diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index a501f7b3e8..913663c509 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -1,5 +1,6 @@ import { defineConfig } from "cypress"; import mochawesome from "cypress-mochawesome-reporter/plugin.js"; +import fs from "fs"; let globalState; @@ -28,7 +29,18 @@ export default defineConfig({ return null; }, }); - + on("after:spec", (spec, results) => { + if (results && results.video) { + // Do we have failures for any retry attempts? + const failures = results.tests.some((test) => + test.attempts.some((attempt) => attempt.state === "failed") + ); + if (!failures) { + // delete the video if the spec passed and no tests retried + fs.unlinkSync(results.video); + } + } + }); return config; }, experimentalRunAllSpecs: true, @@ -47,5 +59,8 @@ export default defineConfig({ chromeWebSecurity: false, defaultCommandTimeout: 10000, pageLoadTimeout: 20000, + responseTimeout: 30000, screenshotsFolder: screenshotsFolderName, + video: true, + videoCompression: 32, }); diff --git a/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js b/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js index 89b27a6b27..0f754831d4 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js @@ -6,6 +6,7 @@ import { cardCreditEnabled, cardCreditEnabledInUs, cardCreditEnabledInUsd, + cardCreditEnabledInEur, createPaymentBodyWithCurrency, createPaymentBodyWithCurrencyCountry, } from "../PaymentMethodListUtils/Commons"; @@ -68,7 +69,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as EUR and no billing address it("create-payment-call-test", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -88,7 +90,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListWithStripeForIdeal" ]; cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( @@ -151,7 +153,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as INR and no billing address it("create-payment-call-test", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -171,7 +174,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListNull" ]; cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( @@ -234,7 +237,8 @@ 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", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -254,7 +258,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; cy.paymentMethodListTestTwoConnectorsForOnePaymentMethodCredit( @@ -317,7 +321,8 @@ 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", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -337,7 +342,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which shouldn't have anything it("payment-method-list-call-test", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListNull" ]; cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( @@ -402,7 +407,8 @@ 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", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -422,7 +428,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; cy.paymentMethodListTestTwoConnectorsForOnePaymentMethodCredit( @@ -486,7 +492,8 @@ 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", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -506,7 +513,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; cy.paymentMethodListTestTwoConnectorsForOnePaymentMethodCredit( @@ -569,7 +576,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as EUR and no billing address it("create-payment-call-test", () => { - const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; const newData = { ...data, @@ -589,7 +597,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", () => { const data = - getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ "PmListWithStripeForIdeal" ]; cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( @@ -599,4 +607,179 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); } ); + + context( + ` + MCA1 -> Stripe configured with credit = { currency = "USD" }\n + MCA2 -> Novalnet configured with credit = { currency = "EUR" }\n + Payment is done with currency as as USD and no billing address\n + The resultant Payment Method list should only have credit with stripe\n + `, + () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("merchant-create-call-test", () => { + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); + }); + + it("api-key-create-call-test", () => { + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + // stripe connector create with card credit enabled in USD + it("connector-create-call-test", () => { + cy.createNamedConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + cardCreditEnabledInUsd, + globalState, + "stripe", + "stripe_US_default" + ); + }); + + // novalnet connector create with card credit enabled in EUR + it("connector-create-call-test", () => { + cy.createNamedConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + cardCreditEnabledInEur, + globalState, + "novalnet", + "novalnet_DE_default" + ); + }); + + // creating payment with currency as USD and no billing email + // billing.email is mandatory for novalnet + it("create-payment-call-test", () => { + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; + const newData = { + ...data, + Request: data.RequestCurrencyUSD, + RequestCurrencyUSD: undefined, // we do not need this anymore + }; + + cy.createPaymentIntentTest( + createPaymentBodyWithCurrency("USD"), + newData, + "no_three_ds", + "automatic", + globalState + ); + }); + + // payment method list should only have credit with stripe + it("payment-method-list-call-test", () => { + const data = + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ + "PmListWithCreditOneConnector" + ]; + cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( + data, + globalState + ); + }); + } + ); + context( + ` + MCA1 -> Stripe configured with credit = { currency = "USD" }\n + MCA2 -> Novalnet configured with credit = { currency = "EUR" }\n + Payment is done with currency as as EUR and billing address for 3ds credit card\n + The resultant Payment Method list should only have credit with novalnet\n + `, + () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("merchant-create-call-test", () => { + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); + }); + + it("api-key-create-call-test", () => { + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + // stripe connector create with card credit enabled in USD + it("connector-create-call-test", () => { + cy.createNamedConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + cardCreditEnabledInUsd, + globalState, + "stripe", + "stripe_US_default" + ); + }); + + // novalnet connector create with card credit enabled in EUR + it("connector-create-call-test", () => { + cy.createNamedConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + cardCreditEnabledInEur, + globalState, + "novalnet", + "novalnet_DE_default" + ); + }); + + // creating payment with currency as EUR and billing email + // billing.email is mandatory for novalnet + it("create-payment-call-test", () => { + const data = + getConnectorDetails("connector")["pm_list"]["PaymentIntent"]; + const newData = { + ...data, + Request: data.RequestCurrencyEUR, + RequestCurrencyEUR: undefined, // we do not need this anymore + }; + + cy.createPaymentIntentTest( + createPaymentBodyWithCurrencyCountry("EUR", "IN", "IN"), + newData, + "three_ds", + "automatic", + globalState + ); + }); + + // payment method list should only have credit with novalnet + it("payment-method-list-call-test", () => { + const data = + getConnectorDetails("connector")["pm_list"]["PmListResponse"][ + "PmListWithCreditOneConnector" + ]; + cy.paymentMethodListTestLessThanEqualToOnePaymentMethod( + data, + globalState + ); + }); + } + ); }); diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js index ae72465b60..e782b32368 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js @@ -58,6 +58,26 @@ export const cardCreditEnabledInUs = [ }, ]; +export const cardCreditEnabledInEur = [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: ["Visa"], + minimum_amount: 0, + accepted_currencies: { + type: "enable_only", + list: ["EUR"], + }, + maximum_amount: 68607706, + recurring_enabled: false, + installment_payment_enabled: true, + }, + ], + }, +]; + export const bankRedirectIdealEnabled = [ { payment_method: "bank_redirect", diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Connector.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js rename to cypress-tests/cypress/e2e/PaymentMethodListUtils/Connector.js diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js index f7d199164f..64e127608a 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js @@ -1,9 +1,9 @@ import { connectorDetails as CommonConnectorDetails } from "./Commons.js"; -import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; +import { connectorDetails as ConnectorDetails } from "./Connector.js"; const connectorDetails = { commons: CommonConnectorDetails, - stripe: stripeConnectorDetails, + connector: ConnectorDetails, }; export default function getConnectorDetails(connectorId) { diff --git a/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js index 0050aa39bf..68582a8e9a 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js @@ -54,6 +54,13 @@ describe("Card - NoThreeDS Manual payment flow test", () => { 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("void-call-test", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -149,6 +156,13 @@ describe("Card - NoThreeDS Manual payment flow test", () => { 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("void-call-test", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" diff --git a/cypress-tests/cypress/e2e/PaymentTest/00024-SessionCall.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00024-SessionCall.cy.js new file mode 100644 index 0000000000..8d729a2f77 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00024-SessionCall.cy.js @@ -0,0 +1,50 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Customer 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); + }); + + const 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", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + 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("session-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "SessionToken" + ]; + + cy.sessionTokenCall(fixtures.sessionTokenBody, data, globalState); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00025-BusinessProfileConfigs.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00025-BusinessProfileConfigs.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00028-MemoryCacheConfigs.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00028-MemoryCacheConfigs.cy.js new file mode 100644 index 0000000000..afa7ceda0d --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00028-MemoryCacheConfigs.cy.js @@ -0,0 +1,35 @@ +import State from "../../utils/State"; + +let globalState; + +describe("In Memory Cache Test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Config flows", () => { + const key = "test-key"; + const value = "test value"; + const newValue = "new test value"; + + it("Create Configs", () => { + cy.createConfigs(globalState, key, value); + cy.fetchConfigs(globalState, key, value); + }); + + it("Update Configs", () => { + cy.updateConfigs(globalState, key, newValue); + cy.fetchConfigs(globalState, key, newValue); + }); + + it("delete configs", () => { + cy.deleteConfigs(globalState, key, newValue); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 85718d8f9b..998c6b91f7 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -398,6 +398,27 @@ export const payment_methods_enabled = [ }, ], }, + { + payment_method: "wallet", + payment_method_types: [ + { + payment_method_type: "apple_pay", + minimum_amount: 1, + maximum_amount: 68607706, + recurring_enabled: true, + installment_payment_enabled: true, + payment_experience: "invoke_sdk_client", + }, + { + payment_method_type: "google_pay", + minimum_amount: 1, + maximum_amount: 68607706, + recurring_enabled: true, + installment_payment_enabled: true, + payment_experience: "invoke_sdk_client", + }, + ], + }, ]; export const connectorDetails = { @@ -684,6 +705,14 @@ export const connectorDetails = { setup_future_usage: "on_session", }, }), + SessionToken: { + Response: { + status: 200, + body: { + session_token: [], + }, + }, + }, No3DSManualCapture: getCustomExchange({ Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js index b98973250e..f918fb2592 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js @@ -12,7 +12,7 @@ const successfulNo3DSCardDetails = { }; const successfulThreeDSTestCardDetails = { - card_number: "4000000000001091", + card_number: "4000000000002701", card_exp_month: "01", card_exp_year: "50", card_holder_name: "joseph Doe", @@ -79,7 +79,7 @@ const payment_method_data_no3ds = { const payment_method_data_3ds = { card: { - last4: "1091", + last4: "2701", card_type: "CREDIT", card_network: "Visa", card_issuer: "INTL HDQTRS-CENTER OWNED", @@ -151,6 +151,23 @@ export const connectorDetails = { }, }, }, + SessionToken: { + Response: { + status: 200, + body: { + session_token: [ + { + wallet_name: "apple_pay", + connector: "cybersource", + }, + { + wallet_name: "google_pay", + connector: "cybersource", + }, + ], + }, + }, + }, PaymentIntentWithShippingCost: { Request: { currency: "USD", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js new file mode 100644 index 0000000000..591e6c8869 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js @@ -0,0 +1,214 @@ +const successful3DSCardDetails = { + card_number: "4761739090000088", + card_exp_month: "12", + card_exp_year: "2034", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + currency: "USD", + 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: successful3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSManualCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + }, + }, + }, + }, + No3DSAutoCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + }, + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + 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: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 996356117a..a707643248 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -382,6 +382,21 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + amount: 6500, + authentication_type: "no_three_ds", + currency: "USD", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, PaymentMethodIdMandateNo3DSAutoCapture: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js index 0a8235141d..1025b47411 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -6,6 +6,33 @@ const successfulThreeDSTestCardDetails = { card_cvc: "123", }; +const successfulNo3DSCardDetails = { + card_number: "4200000000000000", + card_exp_month: "03", + 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: "127.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "EUR", + }, + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -206,5 +233,284 @@ export const connectorDetails = { }, }, }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + 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", + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "EUR", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "succeeded", + }, + }, + }, + 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 Novalnet is not implemented", + code: "IR_00", + }, + }, + }, + }, + }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["novalnet"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "first_name", + field_type: "user_full_name", + value: null, + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "last_name", + field_type: "user_full_name", + value: null, + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email_address", + field_type: "user_email_address", + value: "hyperswitch_sdk_demo_id@gmail.com", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["novalnet"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "first_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "last_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email_address", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["novalnet"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "first_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "last_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email_address", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["novalnet"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "first_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "last_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email_address", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + }, + }, + ], + }, + ], + }, + }, }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js index b8dd275afa..8d4d94fa56 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js @@ -163,6 +163,23 @@ export const connectorDetails = { }, }, }, + SessionToken: { + Response: { + status: 200, + body: { + session_token: [ + { + wallet_name: "apple_pay", + connector: "stripe", + }, + { + wallet_name: "google_pay", + connector: "stripe", + }, + ], + }, + }, + }, PaymentIntentWithShippingCost: { Request: { currency: "USD", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index 72bd645134..011383e887 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -26,6 +26,8 @@ import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; import { connectorDetails as trustpayConnectorDetails } from "./Trustpay.js"; import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js"; import { connectorDetails as worldpayConnectorDetails } from "./WorldPay.js"; +import { connectorDetails as deutschebankConnectorDetails } from "./Deutschebank.js"; +import { connectorDetails as xenditConnectorDetails } from "./Xendit.js"; const connectorDetails = { adyen: adyenConnectorDetails, @@ -34,6 +36,7 @@ const connectorDetails = { checkout: checkoutConnectorDetails, commons: CommonConnectorDetails, cybersource: cybersourceConnectorDetails, + deutschebank: deutschebankConnectorDetails, fiservemea: fiservemeaConnectorDetails, iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, @@ -42,6 +45,7 @@ const connectorDetails = { nmi: nmiConnectorDetails, novalnet: novalnetConnectorDetails, paybox: payboxConnectorDetails, + xendit: xenditConnectorDetails, paypal: paypalConnectorDetails, stripe: stripeConnectorDetails, elavon: elavonConnectorDetails, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js index a58bbb22f8..a811cac786 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js @@ -56,14 +56,14 @@ const paymentMethodDataNoThreeDsResponse = { card_extended_bin: null, card_exp_month: "10", card_exp_year: "30", - card_holder_name: null, + card_holder_name: "morino", payment_checks: null, authentication_data: null, }, billing: null, }; -const payment_method_data_3ds = { +const paymentMethodDataThreeDsResponse = { card: { last4: "1091", card_type: "CREDIT", @@ -74,7 +74,7 @@ const payment_method_data_3ds = { card_extended_bin: null, card_exp_month: "10", card_exp_year: "30", - card_holder_name: null, + card_holder_name: "morino", payment_checks: null, authentication_data: null, }, @@ -328,7 +328,7 @@ export const connectorDetails = { body: { status: "requires_customer_action", setup_future_usage: "on_session", - payment_method_data: payment_method_data_3ds, + payment_method_data: paymentMethodDataThreeDsResponse, }, }, }, @@ -349,7 +349,7 @@ export const connectorDetails = { body: { status: "requires_customer_action", setup_future_usage: "on_session", - payment_method_data: payment_method_data_3ds, + payment_method_data: paymentMethodDataThreeDsResponse, }, }, }, @@ -512,6 +512,73 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + amount: 6500, + authentication_type: "no_three_ds", + currency: "USD", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + 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: successfulNoThreeDsCardDetailsRequest, + }, + 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, + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Xendit.js b/cypress-tests/cypress/e2e/PaymentUtils/Xendit.js new file mode 100644 index 0000000000..fcc303877b --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Xendit.js @@ -0,0 +1,624 @@ +const successfulNo3DSCardDetails = { + card_number: "4000000000001091", + card_exp_month: "12", + card_exp_year: "27", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; +const billingDetails = { + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, +}; +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", + }, +}; +const paymentMethodData3ds = { + 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: "12", + card_exp_year: "27", + card_holder_name: "joseph Doe", + payment_checks: null, + authentication_data: null, + }, + billing: { + address: null, + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 1600000, + currency: "IDR", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "IDR", + }, + }, +}; +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "IDR", + customer_acceptance: null, + setup_future_usage: "on_session", + amount: 6500000, + billing: billingDetails, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "IDR", + shipping_cost: 100, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 100, + amount: 6500000, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + amount: 6500000, + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + shipping_cost: 100, + amount: 6500000, + }, + }, + }, + No3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + amount: 6500000, + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + No3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + amount: 6500000, + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + "3DSAutoCapture": { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: paymentMethodData3ds, + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 1000, + }, + }, + Request: { + payment_method: "card", + amount: 6500000, + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + "3DSManualCapture": { + Request: { + amount: 6500000, + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: paymentMethodData3ds, + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + amount: 6500000, + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: billingDetails, + }, + currency: "IDR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + Capture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + amount: 6500000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500000, + amount_capturable: 0, + amount_received: 6500000, + }, + }, + }, + PartialCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + amount: 6500000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500000, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + VoidAfterConfirm: { + Request: {}, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Cancel/Void flow is not supported", + code: "IR_19", + }, + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "IDR", + billing: billingDetails, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 3000, + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: billingDetails, + currency: "IDR", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js index 22e4b3783a..5699712ec2 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js @@ -7,7 +7,16 @@ let globalState; describe("Priority Based Routing Test", () => { let shouldContinue = true; - context("Login", () => { + beforeEach(() => { + // Restore the session if it exists + cy.session("login", () => { + cy.userLogin(globalState); + cy.terminate2Fa(globalState); + cy.userInfo(globalState); + }); + }); + + context("Get merchant info", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { globalState = new State(state); @@ -18,12 +27,6 @@ describe("Priority Based Routing Test", () => { cy.task("setGlobalState", globalState.data); }); - it("User login", () => { - cy.userLogin(globalState); - cy.terminate2Fa(globalState); - cy.userInfo(globalState); - }); - it("merchant retrieve call", () => { cy.merchantRetrieveCall(globalState); }); @@ -39,6 +42,7 @@ describe("Priority Based Routing Test", () => { after("flush global state", () => { cy.task("setGlobalState", globalState.data); }); + it("list-mca-by-mid", () => { cy.ListMcaByMid(globalState); }); @@ -117,6 +121,7 @@ describe("Priority Based Routing Test", () => { after("flush global state", () => { cy.task("setGlobalState", globalState.data); }); + it("list-mca-by-mid", () => { cy.ListMcaByMid(globalState); }); diff --git a/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js index 7d7f75e351..16b640bc03 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js @@ -5,7 +5,16 @@ import * as utils from "../RoutingUtils/Utils"; let globalState; describe("Volume Based Routing Test", () => { - context("Login", () => { + beforeEach(() => { + // Restore the session if it exists + cy.session("login", () => { + cy.userLogin(globalState); + cy.terminate2Fa(globalState); + cy.userInfo(globalState); + }); + }); + + context("Get merchant info", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { globalState = new State(state); @@ -16,12 +25,6 @@ describe("Volume Based Routing Test", () => { cy.task("setGlobalState", globalState.data); }); - it("User login", () => { - cy.userLogin(globalState); - cy.terminate2Fa(globalState); - cy.userInfo(globalState); - }); - it("merchant retrieve call", () => { cy.merchantRetrieveCall(globalState); }); diff --git a/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js index 304668752c..a1621a530a 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js @@ -5,7 +5,16 @@ import * as utils from "../RoutingUtils/Utils"; let globalState; describe("Rule Based Routing Test", () => { - context("Login", () => { + beforeEach(() => { + // Restore the session if it exists + cy.session("login", () => { + cy.userLogin(globalState); + cy.terminate2Fa(globalState); + cy.userInfo(globalState); + }); + }); + + context("Get merchant info", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { globalState = new State(state); @@ -16,12 +25,6 @@ describe("Rule Based Routing Test", () => { cy.task("setGlobalState", globalState.data); }); - it("User login", () => { - cy.userLogin(globalState); - cy.terminate2Fa(globalState); - cy.userInfo(globalState); - }); - it("merchant retrieve call", () => { cy.merchantRetrieveCall(globalState); }); diff --git a/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js index 74c02d7ac9..94fcaed104 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js @@ -5,7 +5,16 @@ import * as utils from "../RoutingUtils/Utils"; let globalState; describe("Auto Retries & Step Up 3DS", () => { - context("Login", () => { + beforeEach(() => { + // Restore the session if it exists + cy.session("login", () => { + cy.userLogin(globalState); + cy.terminate2Fa(globalState); + cy.userInfo(globalState); + }); + }); + + context("Get merchant info", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { globalState = new State(state); @@ -16,12 +25,6 @@ describe("Auto Retries & Step Up 3DS", () => { cy.task("setGlobalState", globalState.data); }); - it("User login", () => { - cy.userLogin(globalState); - cy.terminate2Fa(globalState); - cy.userInfo(globalState); - }); - it("List MCA", () => { cy.ListMcaByMid(globalState); }); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 6135f26934..1b7b41a035 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1016,21 +1016,56 @@ Cypress.Commands.add( } ); -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( + "sessionTokenCall", + (sessionTokenBody, data, globalState) => { + const { Response: resData } = data || {}; + + sessionTokenBody.payment_id = globalState.get("paymentID"); + sessionTokenBody.client_secret = globalState.get("clientSecret"); + + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/payments/session_tokens`, + headers: { + Accept: "application/json", + "Content-Type": "application/json", + "api-key": globalState.get("publishableKey"), + "x-merchant-domain": "hyperswitch - demo - store.netlify.app", + "x-client-platform": "web", + }, + body: sessionTokenBody, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + if (response.status === 200) { + const expectedTokens = resData.body.session_token; + const actualTokens = response.body.session_token; + + // Verifying length of array + expect(actualTokens.length, "arrayLength").to.equal( + expectedTokens.length + ); + + // Verify specific fields in each session_token object + expectedTokens.forEach((expectedToken, index) => { + const actualToken = actualTokens[index]; + + // Check specific fields only + expect(actualToken.wallet_name, "wallet_name").to.equal( + expectedToken.wallet_name + ); + expect(actualToken.connector, "connector").to.equal( + expectedToken.connector + ); + }); + } else { + defaultErrorHandler(response, resData); + } + }); + } +); Cypress.Commands.add( "createPaymentIntentTest", @@ -2970,14 +3005,13 @@ Cypress.Commands.add("retrievePayoutCallTest", (globalState) => { // User API calls // Below 3 commands should be called in sequence to login a user Cypress.Commands.add("userLogin", (globalState) => { - // Define the necessary variables and constant - const base_url = globalState.get("baseUrl"); - const query_params = `token_only=true`; - const signin_body = { - email: `${globalState.get("email")}`, - password: `${globalState.get("password")}`, + const baseUrl = globalState.get("baseUrl"); + const queryParams = `token_only=true`; + const signinBody = { + email: globalState.get("email"), + password: globalState.get("password"), }; - const url = `${base_url}/user/v2/signin?${query_params}`; + const url = `${baseUrl}/user/v2/signin?${queryParams}`; cy.request({ method: "POST", @@ -2985,37 +3019,38 @@ Cypress.Commands.add("userLogin", (globalState) => { headers: { "Content-Type": "application/json", }, - body: signin_body, + body: signinBody, failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); if (response.status === 200) { if (response.body.token_type === "totp") { - expect(response.body).to.have.property("token").and.to.not.be.empty; + expect(response.body, "totp_token").to.have.property("token").and.to.not + .be.empty; - globalState.set("totpToken", response.body.token); - cy.task("setGlobalState", globalState.data); + const totpToken = response.body.token; + globalState.set("totpToken", totpToken); } } else { 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}"` ); } }); }); Cypress.Commands.add("terminate2Fa", (globalState) => { // Define the necessary variables and constant - const base_url = globalState.get("baseUrl"); - const query_params = `skip_two_factor_auth=true`; - const api_key = globalState.get("totpToken"); - const url = `${base_url}/user/2fa/terminate?${query_params}`; + const baseUrl = globalState.get("baseUrl"); + const queryParams = `skip_two_factor_auth=true`; + const apiKey = globalState.get("totpToken"); + const url = `${baseUrl}/user/2fa/terminate?${queryParams}`; cy.request({ method: "GET", url: url, headers: { - Authorization: `Bearer ${api_key}`, + Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, failOnStatusCode: false, @@ -3024,29 +3059,30 @@ Cypress.Commands.add("terminate2Fa", (globalState) => { if (response.status === 200) { if (response.body.token_type === "user_info") { - expect(response.body).to.have.property("token").and.to.not.be.empty; + expect(response.body, "user_info_token").to.have.property("token").and + .to.not.be.empty; - globalState.set("userInfoToken", response.body.token); - cy.task("setGlobalState", globalState.data); + const userInfoToken = response.body.token; + globalState.set("userInfoToken", userInfoToken); } } else { 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}"` ); } }); }); Cypress.Commands.add("userInfo", (globalState) => { // Define the necessary variables and constant - const base_url = globalState.get("baseUrl"); - const api_key = globalState.get("userInfoToken"); - const url = `${base_url}/user`; + const baseUrl = globalState.get("baseUrl"); + const apiKey = globalState.get("userInfoToken"); + const url = `${baseUrl}/user`; cy.request({ method: "GET", url: url, headers: { - Authorization: `Bearer ${api_key}`, + Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, failOnStatusCode: false, @@ -3054,16 +3090,21 @@ Cypress.Commands.add("userInfo", (globalState) => { logRequestId(response.headers["x-request-id"]); if (response.status === 200) { - expect(response.body).to.have.property("merchant_id").and.to.not.be.empty; - expect(response.body).to.have.property("org_id").and.to.not.be.empty; - expect(response.body).to.have.property("profile_id").and.to.not.be.empty; + expect(response.body, "merchant_id").to.have.property("merchant_id").and + .to.not.be.empty; + expect(response.body, "organization_id").to.have.property("org_id").and.to + .not.be.empty; + expect(response.body, "profile_id").to.have.property("profile_id").and.to + .not.be.empty; globalState.set("merchantId", response.body.merchant_id); globalState.set("organizationId", response.body.org_id); globalState.set("profileId", response.body.profile_id); + + globalState.set("userInfoToken", apiKey); } else { 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}"` ); } }); @@ -3111,7 +3152,6 @@ Cypress.Commands.add( headers: { Authorization: `Bearer ${globalState.get("userInfoToken")}`, "Content-Type": "application/json", - Cookie: `${globalState.get("cookie")}`, }, failOnStatusCode: false, body: routingBody, @@ -3134,15 +3174,14 @@ Cypress.Commands.add( Cypress.Commands.add("activateRoutingConfig", (data, globalState) => { const { Response: resData } = data || {}; - 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) => { @@ -3162,15 +3201,14 @@ Cypress.Commands.add("activateRoutingConfig", (data, globalState) => { Cypress.Commands.add("retrieveRoutingConfig", (data, globalState) => { const { Response: resData } = data || {}; - 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) => { @@ -3331,3 +3369,95 @@ Cypress.Commands.add("incrementalAuth", (globalState, data) => { } }); }); + +Cypress.Commands.add("createConfigs", (globalState, key, value) => { + const base_url = globalState.get("baseUrl"); + const api_key = globalState.get("adminApiKey"); + + cy.request({ + method: "POST", + url: `${base_url}/configs/`, + headers: { + "Content-Type": "application/json", + "api-key": api_key, + }, + body: { + key: key, + value: value, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.status).to.equal(200); + expect(response.body).to.have.property("key").to.equal(key); + expect(response.body).to.have.property("value").to.equal(value); + }); +}); + +Cypress.Commands.add("fetchConfigs", (globalState, key, value) => { + const base_url = globalState.get("baseUrl"); + const api_key = globalState.get("adminApiKey"); + + cy.request({ + method: "GET", + url: `${base_url}/configs/${key}`, + headers: { + "Content-Type": "application/json", + "api-key": api_key, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.status).to.equal(200); + expect(response.body).to.have.property("key").to.equal(key); + expect(response.body).to.have.property("value").to.equal(value); + }); +}); + +Cypress.Commands.add("updateConfigs", (globalState, key, value) => { + const base_url = globalState.get("baseUrl"); + const api_key = globalState.get("adminApiKey"); + + cy.request({ + method: "POST", + url: `${base_url}/configs/${key}`, + headers: { + "Content-Type": "application/json", + "api-key": api_key, + }, + body: { + key: key, + value: value, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.status).to.equal(200); + expect(response.body).to.have.property("key").to.equal(key); + expect(response.body).to.have.property("value").to.equal(value); + }); +}); + +Cypress.Commands.add("deleteConfigs", (globalState, key, value) => { + const base_url = globalState.get("baseUrl"); + const api_key = globalState.get("adminApiKey"); + + cy.request({ + method: "DELETE", + url: `${base_url}/configs/${key}`, + headers: { + "Content-Type": "application/json", + "api-key": api_key, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.status).to.equal(200); + expect(response.body).to.have.property("key").to.equal(key); + expect(response.body).to.have.property("value").to.equal(value); + }); +}); diff --git a/cypress-tests/cypress/support/e2e.js b/cypress-tests/cypress/support/e2e.js index a3e0203027..eab7b99b62 100644 --- a/cypress-tests/cypress/support/e2e.js +++ b/cypress-tests/cypress/support/e2e.js @@ -17,5 +17,14 @@ import "cypress-mochawesome-reporter/register"; import "./commands"; -// Alternatively you can use CommonJS syntax: -// require('./commands') +// Add error handling for dynamic imports +Cypress.on("uncaught:exception", (err, runnable) => { + // Log the error details + // eslint-disable-next-line no-console + console.log( + `Error: ${err.message}\nError occurred in: ${runnable.title}\nStack trace: ${err.stack}` + ); + + // Return false to prevent the error from failing the test + return false; +}); diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index 7c1675690e..cc9c180b35 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -278,107 +278,144 @@ function bankRedirectRedirection( function threeDsRedirection(redirection_url, expected_url, connectorId) { cy.visit(redirection_url.href); - if (connectorId === "adyen") { - cy.get("iframe") - .its("0.contentDocument.body") - .within(() => { - cy.get('input[type="password"]').click(); - cy.get('input[type="password"]').type("password"); - cy.get("#buttonSubmit").click(); - }); - } else if ( - connectorId === "bankofamerica" || - connectorId === "cybersource" || - connectorId === "wellsfargo" - ) { - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.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(() => { - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.body") - .within(() => { - cy.get('form[name="cardholderInput"]', { timeout: TIMEOUT }) - .should("exist") - .then(() => { - cy.get('input[name="challengeDataEntry"]').click().type("1234"); - cy.get('input[value="SUBMIT"]').click(); - }); - }); - }); - } else if (connectorId === "novalnet") { - cy.get("form", { timeout: WAIT_TIME }) - .should("exist") - .then(() => { - cy.get('input[id="submit"]').click(); - }); - } else if (connectorId === "stripe") { - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.body") - .within(() => { - cy.get("iframe") - .its("0.contentDocument.body") - .within(() => { - cy.get("#test-source-authorize-3ds").click(); - }); - }); - } else if (connectorId === "trustpay") { - cy.get('form[name="challengeForm"]', { timeout: WAIT_TIME }) - .should("exist") - .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); + + switch (connectorId) { + case "adyen": + cy.get("iframe") + .its("0.contentDocument.body") + .within(() => { + cy.get('input[type="password"]').click(); + cy.get('input[type="password"]').type("password"); + cy.get("#buttonSubmit").click(); + }); + break; + + case "bankofamerica": + case "wellsfargo": + cy.get("iframe", { timeout: TIMEOUT }) + .should("be.visible") + .its("0.contentDocument.body") + .should("not.be.empty") + .within(() => { + cy.get( + 'input[type="text"], input[type="password"], input[name="challengeDataEntry"]', + { timeout: TIMEOUT } + ) + .should("be.visible") + .should("be.enabled") + .click() + .type("1234"); + + cy.get('input[value="SUBMIT"], button[type="submit"]', { + timeout: TIMEOUT, + }) + .should("be.visible") + .click(); + }); + break; + + case "cybersource": + cy.url({ timeout: TIMEOUT }).should("include", expected_url.origin); + break; + + case "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(); + }); + }); + break; + + case "nmi": + case "noon": + case "xendit": + cy.get("iframe", { timeout: TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get("iframe", { timeout: TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get('form[name="cardholderInput"]', { timeout: TIMEOUT }) + .should("exist") + .then(() => { + cy.get('input[name="challengeDataEntry"]') + .click() + .type("1234"); + cy.get('input[value="SUBMIT"]').click(); + }); + }); + }); + break; + + case "novalnet": + cy.get("form", { timeout: WAIT_TIME }) + .should("exist") + .then(() => { + cy.get('input[id="submit"]').click(); + }); + break; + + case "stripe": + cy.get("iframe", { timeout: TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get("iframe") + .its("0.contentDocument.body") + .within(() => { + cy.get("#test-source-authorize-3ds").click(); + }); + }); + break; + + case "trustpay": + cy.get('form[name="challengeForm"]', { timeout: WAIT_TIME }) + .should("exist") + .then(() => { + cy.get("#outcomeSelect").select("Approve").should("have.value", "Y"); + cy.get('button[type="submit"]').click(); + }); + break; + + case "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(); + }); + }); + break; + + case "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]; + cy.get("input#otp-input").should("not.be.disabled").type(otp); + cy.get("button.pay-btn").click(); + }); + }); + break; + + default: + cy.wait(WAIT_TIME); } - cy.then(() => { - verifyReturnUrl(redirection_url, expected_url, true); - }); + // Verify return URL after handling the specific connector + verifyReturnUrl(redirection_url, expected_url, true); } function upiRedirection( @@ -423,21 +460,84 @@ function upiRedirection( } function verifyReturnUrl(redirection_url, expected_url, forward_flow) { - if (forward_flow) { - // Handling redirection + if (!forward_flow) return; + + try { if (redirection_url.host.endsWith(expected_url.host)) { - // No CORS workaround needed - cy.window().its("location.origin").should("eq", expected_url.origin); + cy.wait(WAIT_TIME / 2); + + cy.window() + .its("location") + .then((location) => { + // Check page state before taking screenshots + cy.document().then((doc) => { + // For blank page + cy.wrap(doc.body.innerText.trim()).then((text) => { + if (text === "") { + cy.wrap(text).should("eq", ""); + cy.screenshot("blank-page-error"); + } + }); + + // For error pages + const errorPatterns = [ + /4\d{2}/, + /5\d{2}/, + /error/i, + /invalid request/i, + /server error/i, + ]; + + const pageText = doc.body.innerText.toLowerCase(); + cy.wrap(pageText).then((text) => { + if (errorPatterns.some((pattern) => pattern.test(text))) { + cy.wrap(text).should((content) => { + expect(errorPatterns.some((pattern) => pattern.test(content))) + .to.be.true; + }); + cy.screenshot(`error-page-${Date.now()}`); + } + }); + }); + + const url_params = new URLSearchParams(location.search); + const payment_status = url_params.get("status"); + + if ( + payment_status !== "failed" && + payment_status !== "processing" && + payment_status !== "requires_capture" && + payment_status !== "succeeded" + ) { + cy.wrap(payment_status).should("exist"); + cy.screenshot(`failed-payment-${payment_status}`); + throw new Error( + `Redirection failed with payment status: ${payment_status}` + ); + } + }); } else { - // Workaround for CORS to allow cross-origin iframe cy.origin( expected_url.origin, { args: { expected_url: expected_url.origin } }, ({ expected_url }) => { cy.window().its("location.origin").should("eq", expected_url); + + Cypress.on("uncaught:exception", (err, runnable) => { + // Log the error details + // eslint-disable-next-line no-console + console.log( + `Error: ${err.message}\nError occurred in: ${runnable.title}\nStack trace: ${err.stack}` + ); + // Return false to prevent the error from failing the test + return false; + }); } ); } + } catch (error) { + cy.error("Redirection verification failed:", error); + throw error; } } diff --git a/cypress-tests/package-lock.json b/cypress-tests/package-lock.json index 9fb61a5da8..57aef9f3b3 100644 --- a/cypress-tests/package-lock.json +++ b/cypress-tests/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@eslint/js": "^9.17.0", + "@eslint/js": "^9.18.0", "cypress": "^13.17.0", "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", "eslint-plugin-cypress": "^4.1.0", - "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-prettier": "^5.2.3", "globals": "^15.14.0", "jsqr": "^1.4.0", "prettier": "^3.4.2" @@ -141,11 +141,14 @@ } }, "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==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -188,9 +191,9 @@ } }, "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==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, "license": "MIT", "engines": { @@ -208,12 +211,13 @@ } }, "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==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -1331,19 +1335,19 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "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/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -1391,13 +1395,13 @@ } }, "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==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -1417,9 +1421,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==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/cypress-tests/package.json b/cypress-tests/package.json index 7032a72fb0..2f2f2c692c 100644 --- a/cypress-tests/package.json +++ b/cypress-tests/package.json @@ -19,13 +19,13 @@ "author": "Hyperswitch", "license": "ISC", "devDependencies": { - "@eslint/js": "^9.17.0", + "@eslint/js": "^9.18.0", "cypress": "^13.17.0", "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", "eslint-plugin-cypress": "^4.1.0", - "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-prettier": "^5.2.3", "globals": "^15.14.0", "jsqr": "^1.4.0", "prettier": "^3.4.2" diff --git a/docker-compose.yml b/docker-compose.yml index 7450a65011..4bc08b09f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,6 +51,14 @@ services: environment: # format -> postgresql://DB_USER:DB_PASSWORD@HOST:PORT/DATABASE_NAME - DATABASE_URL=postgresql://db_user:db_pass@pg:5432/hyperswitch_db + + mailhog: + image: mailhog/mailhog + networks: + - router_net + ports: + - "1025:1025" + - "8025:8025" ### Application services hyperswitch-server: diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index feed9b9cb6..d9314ceb91 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -34,6 +34,7 @@ Please join us! - [Resolving a Bug Report](#resolving-a-bug-report) - [Pull Requests](#pull-requests) - [Cargo Commands](#cargo-commands) + - [Code Coverage](#code-coverage) - [Commits](#commits) - [Opening the Pull Request](#opening-the-pull-request) - [Discuss and update](#discuss-and-update) @@ -223,6 +224,74 @@ unstable features: cargo +nightly fmt ``` +### Code Coverage + +We appreciate well-tested code, so feel free to add tests when you can. + +To generate code coverage using the cypress tests, follow these steps: + +0. Make sure `grcov` and `llvm-tools-preview` are installed + + ```shell + rustup component add llvm-tools-preview + cargo install grcov + ``` + +1. Build the project with the `-Cinstrument-coverage` flag: + + ```shell + RUSTFLAGS="-Cinstrument-coverage" cargo build --bin=router --package=router + ``` + + Several `.profraw` files will be generated. (Due to the execution of build scripts) + +2. Execute the binary: + + ```shell + LLVM_PROFILE_FILE="coverage.profraw" target/debug/router + ``` + +3. Open a separate terminal tab and run the cypress tests, following the [README][cypress-v2-readme] + +4. After the tests have finished running, stop the `router` process using `Ctrl+C` + + The generated `coverage.profraw` file will contain the code coverage data for `router` + +5. Generate an html report from the data: + + ```shell + grcov . --source-dir . --output-type html --binary-path ./target/debug + ``` + +6. A folder named `html` will be generated, containing the report. You can view it using: + + ```shell + cd html && python3 -m http.server 8000 + ``` + +7. You can delete the generated `.profraw` files: + + ```shell + rm **/*.profraw + ``` + +Note: +- It is necessary to stop the `router` process to generate the coverage file +- Branch coverage generation requires nightly and currently `grcov` crashes while trying to include branch coverage. (Checked using `--log-level` parameter in `grcov`) + +#### Integration with VSCode +You can also visualize code coverage in VSCode using the [coverage-gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) extension. + +You need to generate an `lcov.info` file in the directory root. After following till step 4 above: + +```shell +grcov . -s . -t lcov --output-path lcov.info --binary-path ./target/debug --keep-only "crates/*" +``` + +This will generate an `lcov.info` file that can be read by the extension. + +[cypress-v2-readme]: /cypress-tests-v2/README.md + ### Commits It is a recommended best practice to keep your changes as logically grouped as diff --git a/docs/imgs/features.png b/docs/imgs/features.png new file mode 100644 index 0000000000..06b54c123b Binary files /dev/null and b/docs/imgs/features.png differ diff --git a/docs/imgs/hyperswitch-architecture-v1.png b/docs/imgs/hyperswitch-architecture-v1.png new file mode 100644 index 0000000000..f3810e2c26 Binary files /dev/null and b/docs/imgs/hyperswitch-architecture-v1.png differ diff --git a/docs/imgs/non-functional-features.png b/docs/imgs/non-functional-features.png new file mode 100644 index 0000000000..7afe183763 Binary files /dev/null and b/docs/imgs/non-functional-features.png differ diff --git a/justfile b/justfile index 43fca4afc8..5a0d13396b 100644 --- a/justfile +++ b/justfile @@ -64,6 +64,22 @@ check_v2 *FLAGS: cargo check {{ check_flags }} --no-default-features --features "${FEATURES}" -- {{ FLAGS }} set +x +build_v2 *FLAGS: + #! /usr/bin/env bash + set -euo pipefail + + FEATURES="$(cargo metadata --all-features --format-version 1 --no-deps | \ + jq -r ' + [ .packages[] | select(.name == "router") | .features | keys[] # Obtain features of `router` package + | select( any( . ; test("(([a-z_]+)_)?v2") ) ) ] # Select v2 features + | join(",") # Construct a comma-separated string of features for passing to `cargo` + ')" + + set -x + cargo build --package router --bin router --no-default-features --features "${FEATURES}" {{ FLAGS }} + set +x + + run_v2: #! /usr/bin/env bash set -euo pipefail diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index ec58ab08b8..e90eb16ab6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -370,7 +370,7 @@ card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"} [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [analytics] source = "sqlx" @@ -400,7 +400,7 @@ keys = "accept-language,user-agent,x-profile-id" [multitenancy] enabled = false -global_tenant = { schema = "public", redis_key_prefix = "" } +global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" } [multitenancy.tenants.public] base_url = "http://localhost:8080" diff --git a/migrations/2024-11-13-105952_add_call-back-mapper_table/down.sql b/migrations/2024-11-13-105952_add_call-back-mapper_table/down.sql new file mode 100644 index 0000000000..8c2f4f54a1 --- /dev/null +++ b/migrations/2024-11-13-105952_add_call-back-mapper_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS callback_mapper; \ No newline at end of file diff --git a/migrations/2024-11-13-105952_add_call-back-mapper_table/up.sql b/migrations/2024-11-13-105952_add_call-back-mapper_table/up.sql new file mode 100644 index 0000000000..604034f493 --- /dev/null +++ b/migrations/2024-11-13-105952_add_call-back-mapper_table/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS callback_mapper ( + id VARCHAR(128) NOT NULL, + type VARCHAR(64) NOT NULL, + data JSONB NOT NULL, + created_at TIMESTAMP NOT NULL, + last_modified_at TIMESTAMP NOT NULL, + PRIMARY KEY (id, type) +); \ No newline at end of file diff --git a/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql new file mode 100644 index 0000000000..74679837d3 --- /dev/null +++ b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE refund DROP COLUMN IF EXISTS unified_code; +ALTER TABLE refund DROP COLUMN IF EXISTS unified_message; \ No newline at end of file diff --git a/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql new file mode 100644 index 0000000000..3d350c790f --- /dev/null +++ b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS unified_code VARCHAR(255) DEFAULT NULL, +ADD COLUMN IF NOT EXISTS unified_message VARCHAR(1024) DEFAULT NULL; \ No newline at end of file diff --git a/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql new file mode 100644 index 0000000000..58d6b900e2 --- /dev/null +++ b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE roles DROP COLUMN IF EXISTS tenant_id; \ No newline at end of file diff --git a/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql new file mode 100644 index 0000000000..13ec3cf48b --- /dev/null +++ b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE roles ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) NOT NULL DEFAULT 'public'; \ No newline at end of file diff --git a/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/down.sql b/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/down.sql new file mode 100644 index 0000000000..c99db9a384 --- /dev/null +++ b/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE dynamic_routing_stats +DROP COLUMN IF EXISTS global_success_based_connector; \ No newline at end of file diff --git a/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/up.sql b/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/up.sql new file mode 100644 index 0000000000..81e00a9753 --- /dev/null +++ b/migrations/2025-01-07-101337_global_sr_connector_dynamic_routing/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE dynamic_routing_stats +ADD COLUMN IF NOT EXISTS global_success_based_connector VARCHAR(64); \ No newline at end of file diff --git a/migrations/2025-01-07-105739_create_index_for_relay/down.sql b/migrations/2025-01-07-105739_create_index_for_relay/down.sql new file mode 100644 index 0000000000..8a75d44546 --- /dev/null +++ b/migrations/2025-01-07-105739_create_index_for_relay/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP INDEX relay_profile_id_connector_reference_id_index; \ No newline at end of file diff --git a/migrations/2025-01-07-105739_create_index_for_relay/up.sql b/migrations/2025-01-07-105739_create_index_for_relay/up.sql new file mode 100644 index 0000000000..5ebe5983c7 --- /dev/null +++ b/migrations/2025-01-07-105739_create_index_for_relay/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +CREATE UNIQUE INDEX relay_profile_id_connector_reference_id_index ON relay (profile_id, connector_reference_id); \ No newline at end of file diff --git a/proto/success_rate.proto b/proto/success_rate.proto index 38e56e36c0..17d566af35 100644 --- a/proto/success_rate.proto +++ b/proto/success_rate.proto @@ -7,6 +7,8 @@ service SuccessRateCalculator { rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse); rpc InvalidateWindows (InvalidateWindowsRequest) returns (InvalidateWindowsResponse); + + rpc FetchEntityAndGlobalSuccessRate (CalGlobalSuccessRateRequest) returns (CalGlobalSuccessRateResponse); } // API-1 types @@ -20,6 +22,12 @@ message CalSuccessRateRequest { message CalSuccessRateConfig { uint32 min_aggregates_size = 1; double default_success_rate = 2; + optional SuccessRateSpecificityLevel specificity_level = 3; +} + +enum SuccessRateSpecificityLevel { + ENTITY = 0; + GLOBAL = 1; } message CalSuccessRateResponse { @@ -31,12 +39,13 @@ message LabelWithScore { string label = 2; } - // API-2 types +// API-2 types message UpdateSuccessRateWindowRequest { string id = 1; string params = 2; repeated LabelWithStatus labels_with_status = 3; UpdateSuccessRateWindowConfig config = 4; + repeated LabelWithStatus global_labels_with_status = 5; } message LabelWithStatus { @@ -55,7 +64,11 @@ message CurrentBlockThreshold { } message UpdateSuccessRateWindowResponse { - string message = 1; + enum UpdationStatus { + WINDOW_UPDATION_SUCCEEDED = 0; + WINDOW_UPDATION_FAILED = 1; + } + UpdationStatus status = 1; } // API-3 types @@ -64,5 +77,28 @@ message InvalidateWindowsRequest { } message InvalidateWindowsResponse { - string message = 1; + enum InvalidationStatus { + WINDOW_INVALIDATION_SUCCEEDED = 0; + WINDOW_INVALIDATION_FAILED = 1; + } + InvalidationStatus status = 1; +} + +// API-4 types +message CalGlobalSuccessRateRequest { + string entity_id = 1; + string entity_params = 2; + repeated string entity_labels = 3; + repeated string global_labels = 4; + CalGlobalSuccessRateConfig config = 5; +} + +message CalGlobalSuccessRateConfig { + uint32 entity_min_aggregates_size = 1; + double entity_default_success_rate = 2; +} + +message CalGlobalSuccessRateResponse { + repeated LabelWithScore entity_scores_with_labels = 1; + repeated LabelWithScore global_scores_with_labels = 2; } \ No newline at end of file diff --git a/scripts/execute_cypress.sh b/scripts/execute_cypress.sh index 1f1219ee71..8d93519b46 100755 --- a/scripts/execute_cypress.sh +++ b/scripts/execute_cypress.sh @@ -158,12 +158,13 @@ function cleanup() { function main() { local command="${1:-}" local jobs="${2:-5}" + local test_dir="${3:-cypress-tests}" - # 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!" + # Ensure script runs from the specified test directory (default: cypress-tests) + if [[ "$(basename "$PWD")" != "$(basename "$test_dir")" ]]; then + print_color "yellow" "Changing directory to '${test_dir}'..." + cd "${test_dir}" || { + print_color "red" "ERROR: Directory '${test_dir}' not found!" exit 1 } fi