diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index c79ffa63709a..ecb13f3c1a85 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -41,17 +41,25 @@ jobs: name: Check formatting runs-on: ubuntu-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout repository with token if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.AUTO_FILE_UPDATE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: Checkout repository for fork if: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@master @@ -71,8 +79,8 @@ jobs: cargo +nightly fmt --all if ! git diff --exit-code --quiet -- crates; then echo "::notice::Formatting check failed" - git config --local user.name 'github-actions[bot]' - git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' git add crates git commit --message 'chore: run formatter' git push @@ -91,7 +99,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Fetch base branch" shell: bash @@ -108,12 +116,12 @@ jobs: with: toolchain: 1.65 - - uses: Swatinem/rust-cache@v2.4.0 + - uses: Swatinem/rust-cache@v2.7.0 with: save-if: ${{ github.event_name == 'push' }} - name: Install cargo-hack - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: cargo-hack version: 0.6.5 @@ -280,7 +288,7 @@ jobs: # steps: # - name: Checkout repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Run cargo-deny # uses: EmbarkStudios/cargo-deny-action@v1.3.2 @@ -299,17 +307,25 @@ jobs: # - windows-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout repository for fork if: ${{ (github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Checkout repository with token if: ${{ (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.AUTO_FILE_UPDATE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: "Fetch base branch" shell: bash @@ -328,16 +344,16 @@ jobs: components: clippy - name: Install cargo-hack - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: cargo-hack # - name: Install cargo-nextest - # uses: baptiste0928/cargo-install@v2.1.0 + # uses: baptiste0928/cargo-install@v2.2.0 # with: # crate: cargo-nextest - - uses: Swatinem/rust-cache@v2.4.0 + - uses: Swatinem/rust-cache@v2.7.0 with: save-if: ${{ github.event_name == 'push' }} @@ -360,8 +376,8 @@ jobs: shell: bash run: | if ! git diff --quiet --exit-code -- Cargo.lock ; then - git config --local user.name 'github-actions[bot]' - git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' git add Cargo.lock git commit --message 'chore: update Cargo.lock' git push @@ -516,7 +532,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Spell check uses: crate-ci/typos@master diff --git a/.github/workflows/CI-push.yml b/.github/workflows/CI-push.yml index edc9317e526d..a6a4bde5a5d4 100644 --- a/.github/workflows/CI-push.yml +++ b/.github/workflows/CI-push.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@master @@ -50,7 +50,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install mold linker uses: rui314/setup-mold@v1 @@ -63,12 +63,12 @@ jobs: with: toolchain: 1.65 - - uses: Swatinem/rust-cache@v2.4.0 + - uses: Swatinem/rust-cache@v2.7.0 with: save-if: ${{ github.event_name == 'push' }} - name: Install cargo-hack - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: cargo-hack version: 0.6.5 @@ -101,7 +101,7 @@ jobs: # steps: # - name: Checkout repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Run cargo-deny # uses: EmbarkStudios/cargo-deny-action@v1.3.2 @@ -121,7 +121,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install mold linker uses: rui314/setup-mold@v1 @@ -136,16 +136,16 @@ jobs: components: clippy - name: Install cargo-hack - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: cargo-hack # - name: Install cargo-nextest - # uses: baptiste0928/cargo-install@v2.1.0 + # uses: baptiste0928/cargo-install@v2.2.0 # with: # crate: cargo-nextest - - uses: Swatinem/rust-cache@v2.4.0 + - uses: Swatinem/rust-cache@v2.7.0 with: save-if: ${{ github.event_name == 'push' }} @@ -178,7 +178,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Spell check uses: crate-ci/typos@master diff --git a/.github/workflows/auto-release-tag.yml b/.github/workflows/auto-release-tag.yml index 5334c914cda5..4555b68764c1 100644 --- a/.github/workflows/auto-release-tag.yml +++ b/.github/workflows/auto-release-tag.yml @@ -10,18 +10,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWD }} - name: Build and push router Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | BINARY=router @@ -30,7 +30,7 @@ jobs: tags: juspaydotin/orca:${{ github.ref_name }}, juspaydotin/hyperswitch-router:${{ github.ref_name }} - name: Build and push consumer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | BINARY=scheduler @@ -40,7 +40,7 @@ jobs: tags: juspaydotin/orca-consumer:${{ github.ref_name }}, juspaydotin/hyperswitch-consumer:${{ github.ref_name }} - name: Build and push producer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | BINARY=scheduler @@ -50,7 +50,7 @@ jobs: tags: juspaydotin/orca-producer:${{ github.ref_name }}, juspaydotin/hyperswitch-producer:${{ github.ref_name }} - name: Build and push drainer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | BINARY=drainer diff --git a/.github/workflows/connector-sanity-tests.yml b/.github/workflows/connector-sanity-tests.yml index 40a3c3612503..48e6a946a450 100644 --- a/.github/workflows/connector-sanity-tests.yml +++ b/.github/workflows/connector-sanity-tests.yml @@ -79,14 +79,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable 2 weeks ago - - uses: Swatinem/rust-cache@v2.4.0 + - uses: Swatinem/rust-cache@v2.7.0 - name: Decrypt connector auth file env: diff --git a/.github/workflows/connector-ui-sanity-tests.yml b/.github/workflows/connector-ui-sanity-tests.yml index 5db45f2962a5..d4317681a113 100644 --- a/.github/workflows/connector-ui-sanity-tests.yml +++ b/.github/workflows/connector-ui-sanity-tests.yml @@ -82,7 +82,7 @@ jobs: - name: Checkout repository if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Decrypt connector auth file if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} @@ -113,10 +113,10 @@ jobs: toolchain: stable - name: Build and Cache Rust Dependencies - uses: Swatinem/rust-cache@v2.4.0 + uses: Swatinem/rust-cache@v2.7.0 - name: Install Diesel CLI with Postgres Support - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} with: crate: diesel_cli diff --git a/.github/workflows/conventional-commit-check.yml b/.github/workflows/conventional-commit-check.yml index 5fd25e9332d1..ad01642068b5 100644 --- a/.github/workflows/conventional-commit-check.yml +++ b/.github/workflows/conventional-commit-check.yml @@ -45,7 +45,7 @@ jobs: with: toolchain: stable 2 weeks ago - - uses: baptiste0928/cargo-install@v2.1.0 + - uses: baptiste0928/cargo-install@v2.2.0 with: crate: cocogitto diff --git a/.github/workflows/create-hotfix-branch.yml b/.github/workflows/create-hotfix-branch.yml index 77a8bad6bc66..6fd2d4947719 100644 --- a/.github/workflows/create-hotfix-branch.yml +++ b/.github/workflows/create-hotfix-branch.yml @@ -8,11 +8,19 @@ jobs: runs-on: ubuntu-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.AUTO_RELEASE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: Check if the input is valid tag shell: bash diff --git a/.github/workflows/create-hotfix-tag.yml b/.github/workflows/create-hotfix-tag.yml index 45699bda24dc..e9df004139e0 100644 --- a/.github/workflows/create-hotfix-tag.yml +++ b/.github/workflows/create-hotfix-tag.yml @@ -8,14 +8,22 @@ jobs: runs-on: ubuntu-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.AUTO_RELEASE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: Install git-cliff - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: git-cliff version: 1.2.0 @@ -86,8 +94,8 @@ jobs: - name: Set Git Configuration shell: bash run: | - git config --local user.name 'github-actions' - git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' - name: Push created commit and tag shell: bash diff --git a/.github/workflows/hotfix-pr-check.yml b/.github/workflows/hotfix-pr-check.yml index 7a724b602586..e178ba31c1e8 100644 --- a/.github/workflows/hotfix-pr-check.yml +++ b/.github/workflows/hotfix-pr-check.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get hotfix pull request body shell: bash diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 0b70631e113d..9ae80047a669 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -17,18 +17,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWD }} - name: Build and push router Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | RUN_ENV=${{ inputs.environment }} @@ -39,7 +39,7 @@ jobs: tags: juspaydotin/orca:${{ github.sha }} - name: Build and push consumer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | RUN_ENV=${{ inputs.environment }} @@ -50,7 +50,7 @@ jobs: tags: juspaydotin/orca-consumer:${{ github.sha }} - name: Build and push producer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | RUN_ENV=${{ inputs.environment }} @@ -61,7 +61,7 @@ jobs: tags: juspaydotin/orca-producer:${{ github.sha }} - name: Build and push drainer Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: build-args: | RUN_ENV=${{ inputs.environment }} diff --git a/.github/workflows/migration-check.yaml b/.github/workflows/migration-check.yaml index 0c4baaa96193..b740bd3a5b77 100644 --- a/.github/workflows/migration-check.yaml +++ b/.github/workflows/migration-check.yaml @@ -40,14 +40,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable 2 weeks ago - - uses: baptiste0928/cargo-install@v2.1.0 + - uses: baptiste0928/cargo-install@v2.2.0 with: crate: diesel_cli features: postgres diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index 3291755b56cf..d5434520715f 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -50,7 +50,7 @@ jobs: steps: - name: Repository checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Decrypt connector auth file if: ${{ ((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')}} @@ -82,11 +82,11 @@ jobs: - name: Build and Cache Rust Dependencies if: ${{ ((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')}} - uses: Swatinem/rust-cache@v2.4.0 + uses: Swatinem/rust-cache@v2.7.0 - name: Install Diesel CLI with Postgres Support if: ${{ ((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')}} - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: diesel_cli features: postgres diff --git a/.github/workflows/pr-title-spell-check.yml b/.github/workflows/pr-title-spell-check.yml index 6ab6f184739d..03b5a8758870 100644 --- a/.github/workflows/pr-title-spell-check.yml +++ b/.github/workflows/pr-title-spell-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Store PR title in a file shell: bash diff --git a/.github/workflows/release-new-version.yml b/.github/workflows/release-new-version.yml index eda2df05153b..b54e240d96fc 100644 --- a/.github/workflows/release-new-version.yml +++ b/.github/workflows/release-new-version.yml @@ -23,11 +23,19 @@ jobs: runs-on: ubuntu-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.AUTO_RELEASE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: Install Rust uses: dtolnay/rust-toolchain@master @@ -35,7 +43,7 @@ jobs: toolchain: stable 2 weeks ago - name: Install cocogitto - uses: baptiste0928/cargo-install@v2.1.0 + uses: baptiste0928/cargo-install@v2.2.0 with: crate: cocogitto version: 5.4.0 @@ -43,8 +51,8 @@ jobs: - name: Set Git Configuration shell: bash run: | - git config --local user.name 'github-actions' - git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' - name: Update Postman collection files from Postman directories shell: bash diff --git a/.github/workflows/validate-openapi-spec.yml b/.github/workflows/validate-openapi-spec.yml index 530c59c9236d..bdb987d625ac 100644 --- a/.github/workflows/validate-openapi-spec.yml +++ b/.github/workflows/validate-openapi-spec.yml @@ -16,24 +16,32 @@ jobs: name: Validate generated OpenAPI spec file runs-on: ubuntu-latest steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + - name: Checkout PR from fork if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Checkout PR with token if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - token: ${{ secrets.AUTO_FILE_UPDATE_PAT }} + token: ${{ steps.generate_token.outputs.token }} - name: Checkout merge group HEAD commit if: ${{ github.event_name == 'merge_group' }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.event.merge_group.head_sha }} @@ -60,8 +68,8 @@ jobs: shell: bash run: | if ! git diff --quiet --exit-code -- openapi/openapi_spec.json ; then - git config --local user.name 'github-actions[bot]' - git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' git add openapi/openapi_spec.json git commit --message 'docs(openapi): re-generate OpenAPI specification' git push diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b86696691e..8b3abf1d5781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,93 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.90.0 (2023-11-27) + +### Features + +- **auth:** Add Authorization for JWT Authentication types ([#2973](https://github.com/juspay/hyperswitch/pull/2973)) ([`03c0a77`](https://github.com/juspay/hyperswitch/commit/03c0a772a99000acf4676db8ca2ce916036281d1)) +- **user:** Implement change password for user ([#2959](https://github.com/juspay/hyperswitch/pull/2959)) ([`bfa1645`](https://github.com/juspay/hyperswitch/commit/bfa1645b847fb881eb2370d5dbfef6fd0b53725d)) + +### Bug Fixes + +- **router:** Added validation to check total orderDetails amount equal to amount in request ([#2965](https://github.com/juspay/hyperswitch/pull/2965)) ([`37532d4`](https://github.com/juspay/hyperswitch/commit/37532d46f599a99e0e021b0455a6f02381005dd7)) +- Add prefix to connector_transaction_id ([#2981](https://github.com/juspay/hyperswitch/pull/2981)) ([`107c3b9`](https://github.com/juspay/hyperswitch/commit/107c3b99417dd7bca7b62741ad601485700f37be)) + +### Refactors + +- **connector:** [Nuvei] update error message ([#2867](https://github.com/juspay/hyperswitch/pull/2867)) ([`04b7c03`](https://github.com/juspay/hyperswitch/commit/04b7c0384dc9290bd60f49033fd35732527720f1)) + +### Testing + +- **postman:** Update postman collection files ([`aee59e0`](https://github.com/juspay/hyperswitch/commit/aee59e088a8e7c1b81aca1015c90c7b4fd07511d)) + +### Documentation + +- **try_local_system:** Add instructions to run using Docker Compose by pulling standalone images ([#2984](https://github.com/juspay/hyperswitch/pull/2984)) ([`0fa8ad1`](https://github.com/juspay/hyperswitch/commit/0fa8ad1b7c27010bf83e4035de9881d29e192e8a)) + +### Miscellaneous Tasks + +- **connector:** Update connector addition script ([#2801](https://github.com/juspay/hyperswitch/pull/2801)) ([`34953a0`](https://github.com/juspay/hyperswitch/commit/34953a046429fe0341e8469bd9b036e176bda205)) + +**Full Changelog:** [`v1.89.0...v1.90.0`](https://github.com/juspay/hyperswitch/compare/v1.89.0...v1.90.0) + +- - - + + +## 1.89.0 (2023-11-24) + +### Features + +- **router:** Add `connector_transaction_id` in error_response from connector flows ([#2972](https://github.com/juspay/hyperswitch/pull/2972)) ([`3322103`](https://github.com/juspay/hyperswitch/commit/3322103f5c9b7c2a5b663980246c6ca36b8dc63e)) + +### Bug Fixes + +- **connector:** [BANKOFAMERICA] Add status VOIDED in enum Bankofameri… ([#2969](https://github.com/juspay/hyperswitch/pull/2969)) ([`203bbd7`](https://github.com/juspay/hyperswitch/commit/203bbd73751e1513206e81d7cf920ec263f83c58)) +- **core:** Error propagation for not supporting partial refund ([#2976](https://github.com/juspay/hyperswitch/pull/2976)) ([`97a38a7`](https://github.com/juspay/hyperswitch/commit/97a38a78e514e4fa3b5db46b6de985be6312dcc3)) +- **router:** Mark refund status as failure for not_implemented error from connector flows ([#2978](https://github.com/juspay/hyperswitch/pull/2978)) ([`d56d805`](https://github.com/juspay/hyperswitch/commit/d56d80557050336d5ed37282f1aa34b6c17389d1)) +- Return none instead of err when payment method data is not found for bank debit during listing ([#2967](https://github.com/juspay/hyperswitch/pull/2967)) ([`5cc829a`](https://github.com/juspay/hyperswitch/commit/5cc829a11f515a413fe19f657a90aa05cebb99b5)) +- Surcharge related status and rules fix ([#2974](https://github.com/juspay/hyperswitch/pull/2974)) ([`3db7213`](https://github.com/juspay/hyperswitch/commit/3db721388a7f0e291d7eb186661fc69a57068ea6)) + +### Documentation + +- **README:** Updated Community Platform Mentions ([#2960](https://github.com/juspay/hyperswitch/pull/2960)) ([`e0bde43`](https://github.com/juspay/hyperswitch/commit/e0bde433282a34eb9eb28a2d9c43c2b17b5e65e5)) +- Add Rust locker information in architecture doc ([#2964](https://github.com/juspay/hyperswitch/pull/2964)) ([`b2f7dd1`](https://github.com/juspay/hyperswitch/commit/b2f7dd13925a1429e316cd9eaf0e2d31d46b6d4a)) + +**Full Changelog:** [`v1.88.0...v1.89.0`](https://github.com/juspay/hyperswitch/compare/v1.88.0...v1.89.0) + +- - - + + +## 1.88.0 (2023-11-23) + +### Features + +- **connector:** [BANKOFAMERICA] Implement Google Pay ([#2940](https://github.com/juspay/hyperswitch/pull/2940)) ([`f91d4ae`](https://github.com/juspay/hyperswitch/commit/f91d4ae11b02def92c1dde743a0c01b5aac5703f)) +- **router:** Allow billing and shipping address update in payments confirm flow ([#2963](https://github.com/juspay/hyperswitch/pull/2963)) ([`59ef162`](https://github.com/juspay/hyperswitch/commit/59ef162219db3e4650dde65710850bc9f3280530)) + +### Bug Fixes + +- **connector:** [Prophetpay] Use refund_id as reference_id for Refund ([#2966](https://github.com/juspay/hyperswitch/pull/2966)) ([`dd3e22a`](https://github.com/juspay/hyperswitch/commit/dd3e22a938714f373477e08d1d25e4b84ac796c6)) +- **core:** Fix Default Values Enum FieldType ([#2934](https://github.com/juspay/hyperswitch/pull/2934)) ([`35a44ed`](https://github.com/juspay/hyperswitch/commit/35a44ed2533b748e3fabb8a2f8db4fa7e5d3cf7e)) +- **drainer:** Increase jobs picked only when stream is not empty ([#2958](https://github.com/juspay/hyperswitch/pull/2958)) ([`42eedf3`](https://github.com/juspay/hyperswitch/commit/42eedf3a8c2e62fc22bcead370d129ebaf11a00b)) +- Amount_captured goes to 0 for 3ds payments ([#2954](https://github.com/juspay/hyperswitch/pull/2954)) ([`75eea7e`](https://github.com/juspay/hyperswitch/commit/75eea7e81787f2e0697b930b82a8188193f8d51f)) +- Make drainer sleep on every loop interval instead of cycle end ([#2951](https://github.com/juspay/hyperswitch/pull/2951)) ([`e8df690`](https://github.com/juspay/hyperswitch/commit/e8df69092f4c6acee58109aaff2a9454fceb571a)) + +### Refactors + +- **connector:** + - [Payeezy] update error message ([#2919](https://github.com/juspay/hyperswitch/pull/2919)) ([`cb65370`](https://github.com/juspay/hyperswitch/commit/cb653706066b889eaa9423a6227ce1df954b4759)) + - [Worldline] change error message from NotSupported to NotImplemented ([#2893](https://github.com/juspay/hyperswitch/pull/2893)) ([`e721b06`](https://github.com/juspay/hyperswitch/commit/e721b06c7077e00458450a4fb98f4497e8227dc6)) + +### Testing + +- **postman:** Update postman collection files ([`9a3fa00`](https://github.com/juspay/hyperswitch/commit/9a3fa00426d74f6d18b3c712b292d98d80d517ba)) + +**Full Changelog:** [`v1.87.0...v1.88.0`](https://github.com/juspay/hyperswitch/compare/v1.87.0...v1.88.0) + +- - - + + ## 1.87.0 (2023-11-22) ### Features diff --git a/README.md b/README.md index 8c5ad9e03b2d..db8e820ef142 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Hyperswitch-Logo

-

The open-source payments switch

@@ -35,7 +34,6 @@ The single API to access payment ecosystems across 130+ countries

-
@@ -57,14 +55,14 @@ Using Hyperswitch, you can:

⚡️ Quick Start Guide

-

One-click deployment on AWS cloud

+### One-click deployment on AWS cloud -The fastest and easiest way to try hyperswitch is via our CDK scripts +The fastest and easiest way to try Hyperswitch 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. @@ -72,12 +70,27 @@ The fastest and easiest way to try hyperswitch is via our CDK scripts For an early access to the production-ready setup fill this Early Access Form +### Run it on your system + +You can run Hyperswitch on your system using Docker Compose after cloning this repository: + +```shell +docker compose up -d +``` + +This will start the payments router, the primary component within Hyperswitch. + +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]. + +[local-setup-guide]: /docs/try_local_system.md +[docker-compose-scheduler-monitoring]: /docs/try_local_system.md#run-the-scheduler-and-monitoring-services +

🔌 Fast Integration for Stripe Users

-If you are already using Stripe, integrating with Hyperswitch is fun, fast & -easy. +If you are already using Stripe, integrating with Hyperswitch is fun, fast & easy. Try the steps below to get a feel for how quick the setup is: 1. Get API keys from our [dashboard]. @@ -96,9 +109,7 @@ Try the steps below to get a feel for how quick the setup is: As of Sept 2023, we support 50+ payment processors and multiple global payment methods. In addition, we are continuously integrating new processors based on their reach and community requests. Our target is to support 100+ processors by H2 2023. -You can find the latest list of payment processors, supported methods, and -features -[here][supported-connectors-and-features]. +You can find the latest list of payment processors, supported methods, and features [here][supported-connectors-and-features]. [supported-connectors-and-features]: https://hyperswitch.io/pm-list @@ -252,11 +263,11 @@ We welcome contributions from the community. Please read through our Included are directions for opening issues, coding standards, and notes on development. -🦀 **Important note for Rust developers**: We aim for contributions from the community -across a broad range of tracks. Hence, we have prioritised simplicity and code -readability over purely idiomatic code. For example, some of the code in core -functions (e.g., `payments_core`) is written to be more readable than -pure-idiomatic. +- We appreciate all types of contributions: code, documentation, demo creation, or some new way you want to contribute to us. + We will reward every contribution with a Hyperswitch branded t-shirt. +- 🦀 **Important note for Rust developers**: We aim for contributions from the community across a broad range of tracks. + Hence, we have prioritised simplicity and code readability over purely idiomatic code. + For example, some of the code in core functions (e.g., `payments_core`) is written to be more readable than pure-idiomatic.

👥 Community

@@ -264,12 +275,10 @@ pure-idiomatic. Get updates on Hyperswitch development and chat with the community: -- Read and subscribe to [the official Hyperswitch blog][blog]. -- Join our [Discord server][discord]. -- Join our [Slack workspace][slack]. -- Ask and explore our [GitHub Discussions][github-discussions]. +- [Discord server][discord] for questions related to contributing to hyperswitch, questions about the architecture, components, etc. +- [Slack workspace][slack] for questions related to integrating hyperswitch, integrating a connector in hyperswitch, etc. +- [GitHub Discussions][github-discussions] to drop feature requests or suggest anything payments-related you need for your stack. -[blog]: https://hyperswitch.io/blog [discord]: https://discord.gg/wJZ7DVW8mm [slack]: https://join.slack.com/t/hyperswitch-io/shared_invite/zt-1k6cz4lee-SAJzhz6bjmpp4jZCDOtOIg [github-discussions]: https://github.com/juspay/hyperswitch/discussions @@ -314,7 +323,6 @@ Check the [CHANGELOG.md](./CHANGELOG.md) file for details. This product is licensed under the [Apache 2.0 License](LICENSE). -

✨ Thanks to all contributors

diff --git a/config/docker_compose.toml b/config/docker_compose.toml index a5294546de41..986240f0a36b 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -15,7 +15,7 @@ level = "DEBUG" # What you see in your terminal. [log.telemetry] traces_enabled = false # Whether traces are enabled. -metrics_enabled = false # Whether metrics are enabled. +metrics_enabled = true # Whether metrics are enabled. ignore_errors = false # Whether to ignore errors during traces or metrics pipeline setup. otel_exporter_otlp_endpoint = "https://otel-collector:4317" # Endpoint to send metrics and traces to. use_xray_generator = false diff --git a/connector-template/test.rs b/connector-template/test.rs index 5bbf761dea19..7b093ddb6efa 100644 --- a/connector-template/test.rs +++ b/connector-template/test.rs @@ -17,6 +17,7 @@ impl utils::Connector for {{project-name | downcase | pascal_case}}Test { connector: Box::new(&{{project-name | downcase | pascal_case}}), connector_name: types::Connector::{{project-name | downcase | pascal_case}}, get_token: types::api::GetToken::Connector, + merchant_connector_id: None, } } diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index c4e4aa90c4b8..ffefaa2ad2c4 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -531,8 +531,8 @@ pub enum FieldType { UserCountry { options: Vec }, //for country inside payment method data ex- bank redirect UserCurrency { options: Vec }, UserBillingName, - UserAddressline1, - UserAddressline2, + UserAddressLine1, + UserAddressLine2, UserAddressCity, UserAddressPincode, UserAddressState, diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 2a896cc38776..4e9f2f284173 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -1,6 +1,6 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; -use crate::user::{ConnectAccountRequest, ConnectAccountResponse}; +use crate::user::{ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse}; impl ApiEventMetric for ConnectAccountResponse { fn get_api_event_type(&self) -> Option { @@ -12,3 +12,5 @@ impl ApiEventMetric for ConnectAccountResponse { } impl ApiEventMetric for ConnectAccountRequest {} + +common_utils::impl_misc_api_event_type!(ChangePasswordRequest); diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 8710c69aa5c6..dfb8e8999771 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -352,6 +352,9 @@ impl SurchargeDetailsResponse { request_surcharge_details.surcharge_amount == self.surcharge_amount && request_surcharge_details.tax_amount.unwrap_or(0) == self.tax_on_surcharge_amount } + pub fn get_total_surcharge_amount(&self) -> i64 { + self.surcharge_amount + self.tax_on_surcharge_amount + } } #[derive(Clone, Debug)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index a077a6d56366..5556c0a282c3 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -312,6 +312,18 @@ pub struct PaymentsRequest { pub payment_type: Option, } +impl PaymentsRequest { + pub fn get_total_capturable_amount(&self) -> Option { + let surcharge_amount = self + .surcharge_details + .map(|surcharge_details| { + surcharge_details.surcharge_amount + surcharge_details.tax_amount.unwrap_or(0) + }) + .unwrap_or(0); + self.amount + .map(|amount| i64::from(amount) + surcharge_amount) + } +} #[derive( Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq, )] @@ -335,6 +347,9 @@ impl RequestSurchargeDetails { final_amount: original_amount + surcharge_amount + tax_on_surcharge_amount, } } + pub fn get_total_surcharge_amount(&self) -> i64 { + self.surcharge_amount + self.tax_amount.unwrap_or(0) + } } #[derive(Default, Debug, Clone, Copy)] diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 91f7702c654e..41ea9cc5193a 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -19,3 +19,9 @@ pub struct ConnectAccountResponse { #[serde(skip_serializing)] pub user_id: String, } + +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct ChangePasswordRequest { + pub new_password: Secret, + pub old_password: Secret, +} diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index 80ae283be85b..a937c785902f 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -264,6 +264,8 @@ pub enum PaymentAttemptUpdate { error_message: Option>, amount_capturable: Option, updated_by: String, + surcharge_amount: Option, + tax_amount: Option, merchant_connector_id: Option, }, RejectUpdate { @@ -291,8 +293,6 @@ pub enum PaymentAttemptUpdate { error_reason: Option>, connector_response_reference_id: Option, amount_capturable: Option, - surcharge_amount: Option, - tax_amount: Option, updated_by: String, authentication_data: Option, encoded_data: Option, @@ -321,11 +321,10 @@ pub enum PaymentAttemptUpdate { error_message: Option>, error_reason: Option>, amount_capturable: Option, - surcharge_amount: Option, - tax_amount: Option, updated_by: String, unified_code: Option>, unified_message: Option>, + connector_transaction_id: Option, }, CaptureUpdate { amount_to_capture: Option, diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 82ab9a1c02e1..9cc6632c638e 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -180,6 +180,8 @@ pub enum PaymentAttemptUpdate { error_code: Option>, error_message: Option>, amount_capturable: Option, + surcharge_amount: Option, + tax_amount: Option, updated_by: String, merchant_connector_id: Option, }, @@ -208,8 +210,6 @@ pub enum PaymentAttemptUpdate { error_reason: Option>, connector_response_reference_id: Option, amount_capturable: Option, - surcharge_amount: Option, - tax_amount: Option, updated_by: String, authentication_data: Option, encoded_data: Option, @@ -238,11 +238,10 @@ pub enum PaymentAttemptUpdate { error_message: Option>, error_reason: Option>, amount_capturable: Option, - surcharge_amount: Option, - tax_amount: Option, updated_by: String, unified_code: Option>, unified_message: Option>, + connector_transaction_id: Option, }, CaptureUpdate { amount_to_capture: Option, @@ -442,6 +441,8 @@ impl From for PaymentAttemptUpdateInternal { amount_capturable, updated_by, merchant_connector_id, + surcharge_amount, + tax_amount, } => Self { amount: Some(amount), currency: Some(currency), @@ -462,6 +463,8 @@ impl From for PaymentAttemptUpdateInternal { amount_capturable, updated_by, merchant_connector_id, + surcharge_amount, + tax_amount, ..Default::default() }, PaymentAttemptUpdate::VoidUpdate { @@ -500,8 +503,6 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, amount_capturable, - surcharge_amount, - tax_amount, updated_by, authentication_data, encoded_data, @@ -523,8 +524,6 @@ impl From for PaymentAttemptUpdateInternal { connector_response_reference_id, amount_capturable, updated_by, - surcharge_amount, - tax_amount, authentication_data, encoded_data, unified_code, @@ -538,11 +537,10 @@ impl From for PaymentAttemptUpdateInternal { error_message, error_reason, amount_capturable, - surcharge_amount, - tax_amount, updated_by, unified_code, unified_message, + connector_transaction_id, } => Self { connector, status: Some(status), @@ -552,10 +550,9 @@ impl From for PaymentAttemptUpdateInternal { error_reason, amount_capturable, updated_by, - surcharge_amount, - tax_amount, unified_code, unified_message, + connector_transaction_id, ..Default::default() }, PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 7ccfd600d662..94a29e3b0a04 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -23,7 +23,7 @@ pub async fn start_drainer( loop_interval: u32, ) -> errors::DrainerResult<()> { let mut stream_index: u8 = 0; - let mut jobs_picked: u8 = 0; + let jobs_picked = Arc::new(atomic::AtomicU8::new(0)); let mut shutdown_interval = tokio::time::interval(std::time::Duration::from_millis(shutdown_interval.into())); @@ -61,15 +61,15 @@ pub async fn start_drainer( stream_index, max_read_count, active_tasks.clone(), + jobs_picked.clone(), )); - jobs_picked += 1; } - (stream_index, jobs_picked) = utils::increment_stream_index( - (stream_index, jobs_picked), + stream_index = utils::increment_stream_index( + (stream_index, jobs_picked.clone()), number_of_streams, - &mut loop_interval, ) .await; + loop_interval.tick().await; } Ok(()) | Err(mpsc::error::TryRecvError::Disconnected) => { logger::info!("Awaiting shutdown!"); @@ -114,18 +114,25 @@ pub async fn redis_error_receiver(rx: oneshot::Receiver<()>, shutdown_channel: m } } +#[router_env::instrument(skip_all)] async fn drainer_handler( store: Arc, stream_index: u8, max_read_count: u64, active_tasks: Arc, + jobs_picked: Arc, ) -> errors::DrainerResult<()> { active_tasks.fetch_add(1, atomic::Ordering::Release); let stream_name = utils::get_drainer_stream_name(store.clone(), stream_index); - let drainer_result = - Box::pin(drainer(store.clone(), max_read_count, stream_name.as_str())).await; + let drainer_result = Box::pin(drainer( + store.clone(), + max_read_count, + stream_name.as_str(), + jobs_picked, + )) + .await; if let Err(error) = drainer_result { logger::error!(?error) @@ -145,11 +152,15 @@ async fn drainer( store: Arc, max_read_count: u64, stream_name: &str, + jobs_picked: Arc, ) -> errors::DrainerResult<()> { let stream_read = match utils::read_from_stream(stream_name, max_read_count, store.redis_conn.as_ref()).await { - Ok(result) => result, + Ok(result) => { + jobs_picked.fetch_add(1, atomic::Ordering::SeqCst); + result + } Err(error) => { if let errors::DrainerError::RedisError(redis_err) = error.current_context() { if let redis_interface::errors::RedisError::StreamEmptyOrNotAvailable = diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index cc64a99e463c..8101abf5028e 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -79,7 +79,7 @@ impl Default for DrainerSettings { num_partitions: 64, max_read_count: 100, shutdown_interval: 1000, // in milliseconds - loop_interval: 500, // in milliseconds + loop_interval: 100, // in milliseconds } } } diff --git a/crates/drainer/src/utils.rs b/crates/drainer/src/utils.rs index 5a995652bb11..2bd9f092f12c 100644 --- a/crates/drainer/src/utils.rs +++ b/crates/drainer/src/utils.rs @@ -1,16 +1,20 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + sync::{atomic, Arc}, +}; use error_stack::IntoReport; use redis_interface as redis; use crate::{ errors::{self, DrainerError}, - logger, metrics, services, + logger, metrics, services, tracing, }; pub type StreamEntries = Vec<(String, HashMap)>; pub type StreamReadResult = HashMap; +#[router_env::instrument(skip_all)] pub async fn is_stream_available(stream_index: u8, store: Arc) -> bool { let stream_key_flag = get_stream_key_flag(store.clone(), stream_index); @@ -127,19 +131,18 @@ pub fn parse_stream_entries<'a>( // Here the output is in the format (stream_index, jobs_picked), // similar to the first argument of the function pub async fn increment_stream_index( - (index, jobs_picked): (u8, u8), + (index, jobs_picked): (u8, Arc), total_streams: u8, - interval: &mut tokio::time::Interval, -) -> (u8, u8) { +) -> u8 { if index == total_streams - 1 { - interval.tick().await; - match jobs_picked { + match jobs_picked.load(atomic::Ordering::SeqCst) { 0 => metrics::CYCLES_COMPLETED_UNSUCCESSFULLY.add(&metrics::CONTEXT, 1, &[]), _ => metrics::CYCLES_COMPLETED_SUCCESSFULLY.add(&metrics::CONTEXT, 1, &[]), } - (0, 0) + jobs_picked.store(0, atomic::Ordering::SeqCst); + 0 } else { - (index + 1, jobs_picked) + index + 1 } } diff --git a/crates/euclid/src/backend/vir_interpreter/types.rs b/crates/euclid/src/backend/vir_interpreter/types.rs index a144cdaafd08..d0eca5fec2ef 100644 --- a/crates/euclid/src/backend/vir_interpreter/types.rs +++ b/crates/euclid/src/backend/vir_interpreter/types.rs @@ -74,6 +74,10 @@ impl Context { } } + if let Some(card_network) = payment_method.card_network { + enum_values.insert(EuclidValue::CardNetwork(card_network)); + } + if let Some(at) = payment.authentication_type { enum_values.insert(EuclidValue::AuthenticationType(at)); } diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index 43033cf9cfd1..f31e908e0dc3 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -21,6 +21,7 @@ pub mod routes { services::{ api, authentication::{self as auth, AuthToken, AuthenticationData}, + authorization::permissions::Permission, ApplicationResponse, }, types::domain::UserEmail, @@ -135,7 +136,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -171,7 +172,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -207,7 +208,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -233,7 +234,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -259,7 +260,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -285,7 +286,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -307,7 +308,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -333,7 +334,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -385,7 +386,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -437,7 +438,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -489,7 +490,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -525,7 +526,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -551,7 +552,7 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth, + &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index a0da9c88ef35..2eddaf3084d7 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -99,7 +99,7 @@ impl Default for super::settings::DrainerSettings { num_partitions: 64, max_read_count: 100, shutdown_interval: 1000, - loop_interval: 500, + loop_interval: 100, } } } @@ -582,7 +582,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line1".to_string(), display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressline1, + field_type: enums::FieldType::UserAddressLine1, value: None, } ), @@ -806,7 +806,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line1".to_string(), display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressline1, + field_type: enums::FieldType::UserAddressLine1, value: None, } ), @@ -1238,7 +1238,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line1".to_string(), display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressline1, + field_type: enums::FieldType::UserAddressLine1, value: None, } ), @@ -1247,7 +1247,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line2".to_string(), display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressline2, + field_type: enums::FieldType::UserAddressLine2, value: None, } ), @@ -2582,7 +2582,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line1".to_string(), display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressline1, + field_type: enums::FieldType::UserAddressLine1, value: None, } ), @@ -3014,7 +3014,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line1".to_string(), display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressline1, + field_type: enums::FieldType::UserAddressLine1, value: None, } ), @@ -3023,7 +3023,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldInfo { required_field: "billing.address.line2".to_string(), display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressline2, + field_type: enums::FieldType::UserAddressLine2, value: None, } ), diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index f51c91f441df..f6384bf0a5c5 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -79,6 +79,7 @@ impl ConnectorCommon for Aci { .join("; ") }), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 676f15d2f564..e101b796b8d4 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -74,6 +74,7 @@ impl ConnectorCommon for Adyen { message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -256,6 +257,7 @@ impl message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -375,6 +377,7 @@ impl message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -546,6 +549,7 @@ impl message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } @@ -716,6 +720,7 @@ impl message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -920,6 +925,7 @@ impl message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -1439,6 +1445,7 @@ impl services::ConnectorIntegration { @@ -929,6 +931,7 @@ fn get_error_response( reason: Some(message.to_string()), status_code, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 884504154e8f..2c8a63a53e5c 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -574,6 +574,7 @@ impl reason: None, status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }) }); let metadata = transaction_response @@ -649,6 +650,7 @@ impl reason: None, status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }) }); let metadata = transaction_response @@ -792,6 +794,7 @@ impl TryFrom types::Error reason: None, status_code, attempt_status: None, + connector_transaction_id: None, } } diff --git a/crates/router/src/connector/bambora.rs b/crates/router/src/connector/bambora.rs index ff6fdcb46769..19849763ed8e 100644 --- a/crates/router/src/connector/bambora.rs +++ b/crates/router/src/connector/bambora.rs @@ -96,6 +96,7 @@ impl ConnectorCommon for Bambora { message: response.message, reason: Some(serde_json::to_string(&response.details).unwrap_or_default()), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index b6e19fa0d296..a01ea72338c5 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -233,6 +233,7 @@ impl ConnectorCommon for Bankofamerica { message, reason: Some(connector_reason), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index f6cda8ac23ce..70db9a6d8797 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -540,6 +540,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }), @@ -596,6 +597,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }), @@ -652,6 +654,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }), @@ -851,7 +854,7 @@ impl From for enums::RefundStatus { BankofamericaRefundStatus::Succeeded | BankofamericaRefundStatus::Transmitted => { Self::Success } - BankofamericaRefundStatus::Failed => Self::Failure, + BankofamericaRefundStatus::Failed | BankofamericaRefundStatus::Voided => Self::Failure, BankofamericaRefundStatus::Pending => Self::Pending, } } @@ -888,6 +891,7 @@ pub enum BankofamericaRefundStatus { Transmitted, Failed, Pending, + Voided, } #[derive(Debug, Deserialize)] diff --git a/crates/router/src/connector/bitpay.rs b/crates/router/src/connector/bitpay.rs index 856d0a9ec9d7..b6bbaafc4a38 100644 --- a/crates/router/src/connector/bitpay.rs +++ b/crates/router/src/connector/bitpay.rs @@ -121,6 +121,7 @@ impl ConnectorCommon for Bitpay { message: response.error, reason: response.message, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index d1aa1fa25ee6..0bc56d4e9955 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -127,6 +127,7 @@ impl ConnectorCommon for Bluesnap { .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: Some(reason), attempt_status: None, + connector_transaction_id: None, } } bluesnap::BluesnapErrors::Auth(error_res) => ErrorResponse { @@ -135,6 +136,7 @@ impl ConnectorCommon for Bluesnap { message: error_res.error_name.clone().unwrap_or(error_res.error_code), reason: Some(error_res.error_description), attempt_status: None, + connector_transaction_id: None, }, bluesnap::BluesnapErrors::General(error_response) => { let (error_res, attempt_status) = if res.status_code == 403 @@ -156,6 +158,7 @@ impl ConnectorCommon for Bluesnap { message: error_response, reason: Some(error_res), attempt_status, + connector_transaction_id: None, } } }; diff --git a/crates/router/src/connector/boku.rs b/crates/router/src/connector/boku.rs index 87e8fd0eb96a..a2ae9d628134 100644 --- a/crates/router/src/connector/boku.rs +++ b/crates/router/src/connector/boku.rs @@ -131,6 +131,7 @@ impl ConnectorCommon for Boku { message: response.message, reason: response.reason, attempt_status: None, + connector_transaction_id: None, }), Err(_) => get_xml_deserialized(res), } @@ -668,6 +669,7 @@ fn get_xml_deserialized(res: Response) -> CustomResult Ok(ErrorResponse { @@ -141,6 +142,7 @@ impl ConnectorCommon for Braintree { message: consts::NO_ERROR_MESSAGE.to_string(), reason: Some(response.errors), attempt_status: None, + connector_transaction_id: None, }), Err(error_msg) => { logger::error!(deserialization_error =? error_msg); diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index bf51973237c5..5069a9fe38d2 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -317,6 +317,7 @@ fn get_error_response( reason: error_reason, status_code: http_code, attempt_status: None, + connector_transaction_id: None, }) } diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index a8d7d6d80504..6749f4189340 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -120,6 +120,7 @@ impl ConnectorCommon for Cashtocode { message: response.error_description, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/cashtocode/transformers.rs b/crates/router/src/connector/cashtocode/transformers.rs index 42e47c077e8c..cfca998e06c3 100644 --- a/crates/router/src/connector/cashtocode/transformers.rs +++ b/crates/router/src/connector/cashtocode/transformers.rs @@ -218,6 +218,7 @@ impl message: error_data.error_description, reason: None, attempt_status: None, + connector_transaction_id: None, }), ), CashtocodePaymentsResponse::CashtoCodeData(response_data) => { diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index ca2556544f90..312a91196de7 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -132,6 +132,7 @@ impl ConnectorCommon for Checkout { .map(|errors| errors.join(" & ")) .or(response.error_type), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 6ad040da2842..90e65c8b0474 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -577,6 +577,7 @@ impl TryFrom> .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), reason: item.response.response_summary, attempt_status: None, + connector_transaction_id: None, }) } else { None @@ -625,6 +626,7 @@ impl TryFrom> .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), reason: item.response.response_summary, attempt_status: None, + connector_transaction_id: None, }) } else { None diff --git a/crates/router/src/connector/coinbase.rs b/crates/router/src/connector/coinbase.rs index 9c0a06a52c90..b294a4474f69 100644 --- a/crates/router/src/connector/coinbase.rs +++ b/crates/router/src/connector/coinbase.rs @@ -109,6 +109,7 @@ impl ConnectorCommon for Coinbase { message: response.error.message, reason: response.error.code, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 417a36145b92..2af40a298ce0 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -168,6 +168,7 @@ impl ConnectorCommon for Cryptopay { message: response.error.message, reason: response.error.reason, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index ce283b12b798..1868611184f9 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -137,6 +137,7 @@ impl ConnectorCommon for Cybersource { message, reason: Some(connector_reason), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 0e81b6b59dff..33b8fa56d00e 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -552,6 +552,7 @@ impl reason: Some(error.reason), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), _ => Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( diff --git a/crates/router/src/connector/dlocal.rs b/crates/router/src/connector/dlocal.rs index 4ae3a292fdae..28ae058286f0 100644 --- a/crates/router/src/connector/dlocal.rs +++ b/crates/router/src/connector/dlocal.rs @@ -136,6 +136,7 @@ impl ConnectorCommon for Dlocal { message: response.message, reason: response.param, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index 9edcd957ff09..961ef005f2f3 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -112,6 +112,7 @@ impl ConnectorCommon for DummyConnector { message: response.error.message, reason: response.error.reason, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/fiserv.rs b/crates/router/src/connector/fiserv.rs index 2bdb7177d941..28b6d932760d 100644 --- a/crates/router/src/connector/fiserv.rs +++ b/crates/router/src/connector/fiserv.rs @@ -152,6 +152,7 @@ impl ConnectorCommon for Fiserv { reason: first_error.field.to_owned(), status_code: res.status_code, attempt_status: None, + connector_transaction_id: None, }) }) .unwrap_or(types::ErrorResponse { @@ -160,6 +161,7 @@ impl ConnectorCommon for Fiserv { reason: None, status_code: res.status_code, attempt_status: None, + connector_transaction_id: None, })) } } diff --git a/crates/router/src/connector/forte.rs b/crates/router/src/connector/forte.rs index 3aa7cee32878..948db00c936f 100644 --- a/crates/router/src/connector/forte.rs +++ b/crates/router/src/connector/forte.rs @@ -131,6 +131,7 @@ impl ConnectorCommon for Forte { message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 26494d349b88..39452e53df17 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -105,6 +105,7 @@ impl ConnectorCommon for Globalpay { message: response.detailed_error_description, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -319,6 +320,7 @@ impl ConnectorIntegration reason: Some(error_response.error_info), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }), @@ -810,6 +811,7 @@ impl TryFrom for types::ErrorResponse { reason: None, status_code: http_code, attempt_status: None, + connector_transaction_id: None, } } } diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index b6ed231e5b50..457928642554 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -137,6 +137,7 @@ impl ConnectorCommon for Noon { message: response.class_description, reason: Some(response.message), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 27a874930bcc..5ff92582051a 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -512,6 +512,7 @@ impl reason: Some(error_message), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), _ => { let connector_response_reference_id = diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index c23114e2a96b..b79b2c892643 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -623,11 +623,9 @@ impl TryFrom for NuveiBIC { | api_models::enums::BankNames::TsbBank | api_models::enums::BankNames::TescoBank | api_models::enums::BankNames::UlsterBank => { - Err(errors::ConnectorError::NotSupported { - message: bank.to_string(), - connector: "Nuvei", - } - .into()) + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Nuvei"), + ))? } } } @@ -693,10 +691,9 @@ impl bank_name.map(NuveiBIC::try_from).transpose()?, ) } - _ => Err(errors::ConnectorError::NotSupported { - message: "Bank Redirect".to_string(), - connector: "Nuvei", - })?, + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Nuvei"), + ))?, }; Ok(Self { payment_option: PaymentOption { @@ -1580,6 +1577,7 @@ fn get_error_response( reason: None, status_code: http_code, attempt_status: None, + connector_transaction_id: None, }) } diff --git a/crates/router/src/connector/opayo.rs b/crates/router/src/connector/opayo.rs index ba0fb2046b7c..73a793adcf70 100644 --- a/crates/router/src/connector/opayo.rs +++ b/crates/router/src/connector/opayo.rs @@ -108,6 +108,7 @@ impl ConnectorCommon for Opayo { message: response.message, reason: response.reason, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index 41d1e6c3d88c..c4f3d3682dca 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -111,6 +111,7 @@ impl ConnectorCommon for Opennode { message: response.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/payeezy.rs b/crates/router/src/connector/payeezy.rs index 33a8ec65152e..0be640f8fbe4 100644 --- a/crates/router/src/connector/payeezy.rs +++ b/crates/router/src/connector/payeezy.rs @@ -124,6 +124,7 @@ impl ConnectorCommon for Payeezy { message: error_messages.join(", "), reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/router/src/connector/payeezy/transformers.rs index e2e837929c41..817ab43ac717 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/router/src/connector/payeezy/transformers.rs @@ -72,11 +72,9 @@ impl TryFrom for PayeezyCardType { utils::CardIssuer::Maestro | utils::CardIssuer::DinersClub | utils::CardIssuer::JCB - | utils::CardIssuer::CarteBlanche => Err(errors::ConnectorError::NotSupported { - message: utils::SELECTED_PAYMENT_METHOD.to_string(), - connector: "Payeezy", - } - .into()), + | utils::CardIssuer::CarteBlanche => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Payeezy"), + ))?, } } } @@ -262,11 +260,9 @@ fn get_payment_method_data( | api::PaymentMethodData::Reward | api::PaymentMethodData::Upi(_) | api::PaymentMethodData::Voucher(_) - | api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotSupported { - message: utils::SELECTED_PAYMENT_METHOD.to_string(), - connector: "Payeezy", - } - .into()), + | api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Payeezy"), + ))?, } } diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 1e67f8a9f350..84367b3a96f6 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -98,6 +98,7 @@ impl ConnectorCommon for Payme { response.status_error_details, response.status_additional_info )), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 24b7f2b3a0bd..092a8b49fd86 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -227,6 +227,7 @@ impl From<(&PaymePaySaleResponse, u16)> for types::ErrorResponse { reason: pay_sale_response.status_error_details.to_owned(), status_code: http_code, attempt_status: None, + connector_transaction_id: None, } } } @@ -310,6 +311,7 @@ impl From<(&SaleQuery, u16)> for types::ErrorResponse { reason: sale_query_response.sale_error_text.clone(), status_code: http_code, attempt_status: None, + connector_transaction_id: None, } } } diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 0e8cff8c0569..4e50bc924b33 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -92,6 +92,7 @@ impl Paypal { message: response.message.clone(), reason: error_reason.or(Some(response.message)), attempt_status: None, + connector_transaction_id: None, }) } } @@ -245,6 +246,7 @@ impl ConnectorCommon for Paypal { message: response.message.clone(), reason, attempt_status: None, + connector_transaction_id: None, }) } } @@ -380,6 +382,7 @@ impl ConnectorIntegration reason: Some(item.response.response_text), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }) @@ -432,7 +434,6 @@ pub struct ProphetpaySyncResponse { pub response_text: String, #[serde(rename = "transactionID")] pub transaction_id: String, - pub response_code: String, } impl @@ -462,11 +463,12 @@ impl Ok(Self { status: enums::AttemptStatus::Failure, response: Err(types::ErrorResponse { - code: item.response.response_code, + code: const_val::NO_ERROR_CODE.to_string(), message: item.response.response_text.clone(), reason: Some(item.response.response_text), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }) @@ -481,7 +483,6 @@ pub struct ProphetpayVoidResponse { pub response_text: String, #[serde(rename = "transactionID")] pub transaction_id: String, - pub response_code: String, } impl @@ -511,11 +512,12 @@ impl Ok(Self { status: enums::AttemptStatus::VoidFailed, response: Err(types::ErrorResponse { - code: item.response.response_code, + code: const_val::NO_ERROR_CODE.to_string(), message: item.response.response_text.clone(), reason: Some(item.response.response_text), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }), ..item.data }) @@ -576,15 +578,12 @@ impl TryFrom<&ProphetpayRouterData<&types::RefundsRouterData>> for Prophet amount: item.amount.to_owned(), card_token: card_token_data.card_token, profile: auth_data.profile_id, - ref_info: item.router_data.connector_request_reference_id.to_owned(), - inquiry_reference: item.router_data.connector_request_reference_id.clone(), + ref_info: item.router_data.request.refund_id.to_owned(), + inquiry_reference: item.router_data.request.refund_id.clone(), action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Refund), }) } else { - Err(errors::ConnectorError::NotImplemented( - "Partial Refund is Not Supported".to_string(), - ) - .into()) + Err(errors::ConnectorError::NotImplemented("Partial Refund".to_string()).into()) } } } @@ -594,8 +593,7 @@ impl TryFrom<&ProphetpayRouterData<&types::RefundsRouterData>> for Prophet pub struct ProphetpayRefundResponse { pub success: bool, pub response_text: String, - pub tran_seq_number: String, - pub response_code: String, + pub tran_seq_number: Option, } impl TryFrom> @@ -609,7 +607,11 @@ impl TryFrom TryFrom> @@ -658,11 +660,12 @@ impl TryFrom { logger::error!(deserialization_error =? error_msg); diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs index 08985ba022fc..898b6ed6d147 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/router/src/connector/rapyd/transformers.rs @@ -458,6 +458,7 @@ impl message: item.response.status.status.unwrap_or_default(), reason: data.failure_message.to_owned(), attempt_status: None, + connector_transaction_id: None, }), ), _ => { @@ -499,6 +500,7 @@ impl message: item.response.status.status.unwrap_or_default(), reason: item.response.status.message, attempt_status: None, + connector_transaction_id: None, }), ), }; diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index 6f3a2b802014..dfb4a7de0811 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -100,6 +100,7 @@ impl ConnectorCommon for Shift4 { message: response.error.message, reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index d836285755d4..1f1dee6b9e1b 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -124,6 +124,7 @@ impl ConnectorCommon for Square { .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: Some(reason), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/stax.rs b/crates/router/src/connector/stax.rs index 024211c8caaa..1a0cc54a128d 100644 --- a/crates/router/src/connector/stax.rs +++ b/crates/router/src/connector/stax.rs @@ -110,6 +110,7 @@ impl ConnectorCommon for Stax { .to_owned(), ), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index ccf843ec78d6..475105c9cebe 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -227,6 +227,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -357,6 +358,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -483,6 +485,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -617,6 +620,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -760,6 +764,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -918,6 +923,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -1041,6 +1047,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -1197,6 +1204,7 @@ impl .unwrap_or(message) }), attempt_status: None, + connector_transaction_id: response.error.payment_intent.map(|pi| pi.id), }) } } @@ -1318,6 +1326,7 @@ impl services::ConnectorIntegration .or(Some(error.message.clone())), status_code: item.http_code, attempt_status: None, + connector_transaction_id: None, }); let connector_metadata = @@ -2788,6 +2789,12 @@ pub struct ErrorDetails { pub message: Option, pub param: Option, pub decline_code: Option, + pub payment_intent: Option, +} + +#[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +pub struct PaymentIntentErrorResponse { + pub id: String, } #[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)] diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 65ab5a7ba58d..2430aac6c19f 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -139,6 +139,7 @@ impl ConnectorCommon for Trustpay { .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: reason.or(response_data.description), attempt_status: None, + connector_transaction_id: None, }) } Err(error_msg) => { @@ -298,6 +299,7 @@ impl ConnectorIntegration TryFrom { - let captured_amount = if self.request.is_psync() { - payment_data - .payment_attempt - .amount_to_capture - .or(Some(payment_data.payment_attempt.get_total_amount())) - } else { - types::Capturable::get_capture_amount(&self.request) - }; - if Some(payment_data.payment_attempt.get_total_amount()) == captured_amount { + let captured_amount = + types::Capturable::get_capture_amount(&self.request, payment_data); + let total_capturable_amount = payment_data.payment_attempt.get_total_amount(); + if Some(total_capturable_amount) == captured_amount { enums::AttemptStatus::Charged } else if captured_amount.is_some() { enums::AttemptStatus::PartialCharged diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 43b6b3a3406d..14b5c67804f6 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -130,6 +130,7 @@ impl ConnectorCommon for Volt { message: response.exception.message.clone(), reason: Some(reason), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index 865dcd5fff35..4fa66f0d8e5d 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -95,6 +95,7 @@ impl ConnectorCommon for Wise { message: e.message.clone(), reason: None, attempt_status: None, + connector_transaction_id: None, }) } else { Ok(types::ErrorResponse { @@ -103,6 +104,7 @@ impl ConnectorCommon for Wise { message: response.message.unwrap_or_default(), reason: None, attempt_status: None, + connector_transaction_id: None, }) } } @@ -112,6 +114,7 @@ impl ConnectorCommon for Wise { message: response.message.unwrap_or_default(), reason: None, attempt_status: None, + connector_transaction_id: None, }), } } @@ -293,6 +296,7 @@ impl services::ConnectorIntegration for Gateway { utils::CardIssuer::Master => Ok(Self::MasterCard), utils::CardIssuer::Discover => Ok(Self::Discover), utils::CardIssuer::Visa => Ok(Self::Visa), - _ => Err(errors::ConnectorError::NotSupported { - message: issuer.to_string(), - connector: "worldline", - } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldline"), + ) .into()), } } diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index ef01aa9a6ada..4edee3624ef6 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -95,6 +95,7 @@ impl ConnectorCommon for Worldpay { message: response.message, reason: response.validation_errors.map(|e| e.to_string()), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/connector/zen.rs b/crates/router/src/connector/zen.rs index 102d54bab427..5164407a2eed 100644 --- a/crates/router/src/connector/zen.rs +++ b/crates/router/src/connector/zen.rs @@ -127,6 +127,7 @@ impl ConnectorCommon for Zen { ), reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 410e3c1113b1..c5490ee00e63 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -58,3 +58,5 @@ pub const LOCKER_REDIS_EXPIRY_SECONDS: u32 = 60 * 15; // 15 minutes #[cfg(any(feature = "olap", feature = "oltp"))] pub const JWT_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24 * 2; // 2 days + +pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin"; diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 3a71fed01a12..c570aca76038 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -1,8 +1,2 @@ -#[cfg(feature = "olap")] pub const MAX_NAME_LENGTH: usize = 70; -#[cfg(feature = "olap")] pub const MAX_COMPANY_NAME_LENGTH: usize = 70; - -// USER ROLES -#[cfg(any(feature = "olap", feature = "oltp"))] -pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin"; diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index b4d48365dc84..b86c395b9814 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -13,6 +13,8 @@ pub enum UserErrors { InvalidCredentials, #[error("UserExists")] UserExists, + #[error("InvalidOldPassword")] + InvalidOldPassword, #[error("EmailParsingError")] EmailParsingError, #[error("NameParsingError")] @@ -27,6 +29,8 @@ pub enum UserErrors { InvalidEmailError, #[error("DuplicateOrganizationId")] DuplicateOrganizationId, + #[error("MerchantIdNotFound")] + MerchantIdNotFound, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -49,6 +53,12 @@ impl common_utils::errors::ErrorSwitch AER::BadRequest(ApiError::new( + sub_code, + 6, + "Old password incorrect. Please enter the correct password", + None, + )), Self::EmailParsingError => { AER::BadRequest(ApiError::new(sub_code, 7, "Invalid Email", None)) } @@ -73,6 +83,9 @@ impl common_utils::errors::ErrorSwitch { + AER::BadRequest(ApiError::new(sub_code, 18, "Invalid Merchant ID", None)) + } } } } diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index 869a5b6bde95..b62abd0e336e 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -136,25 +136,81 @@ pub trait ConnectorErrorExt { impl ConnectorErrorExt for error_stack::Result { fn to_refund_failed_response(self) -> error_stack::Result { - self.map_err(|err| { - let data = match err.current_context() { - errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => { - let response_str = std::str::from_utf8(bytes); - match response_str { - Ok(s) => serde_json::from_str(s) - .map_err( - |error| logger::error!(%error,"Failed to convert response to JSON"), - ) - .ok(), - Err(error) => { - logger::error!(%error,"Failed to convert response to UTF8 string"); - None - } + self.map_err(|err| match err.current_context() { + errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => { + let response_str = std::str::from_utf8(bytes); + let data = match response_str { + Ok(s) => serde_json::from_str(s) + .map_err( + |error| logger::error!(%error,"Failed to convert response to JSON"), + ) + .ok(), + Err(error) => { + logger::error!(%error,"Failed to convert response to UTF8 string"); + None } + }; + err.change_context(errors::ApiErrorResponse::RefundFailed { data }) + } + errors::ConnectorError::NotImplemented(reason) => { + errors::ApiErrorResponse::NotImplemented { + message: errors::api_error_response::NotImplementedMessage::Reason( + reason.to_string(), + ), } - _ => None, - }; - err.change_context(errors::ApiErrorResponse::RefundFailed { data }) + .into() + } + errors::ConnectorError::FailedToObtainIntegrationUrl + | errors::ConnectorError::RequestEncodingFailed + | errors::ConnectorError::RequestEncodingFailedWithReason(_) + | errors::ConnectorError::ParsingFailed + | errors::ConnectorError::ResponseDeserializationFailed + | errors::ConnectorError::UnexpectedResponseError(_) + | errors::ConnectorError::RoutingRulesParsingError + | errors::ConnectorError::FailedToObtainPreferredConnector + | errors::ConnectorError::ProcessingStepFailed(_) + | errors::ConnectorError::InvalidConnectorName + | errors::ConnectorError::InvalidWallet + | errors::ConnectorError::ResponseHandlingFailed + | errors::ConnectorError::MissingRequiredField { .. } + | errors::ConnectorError::MissingRequiredFields { .. } + | errors::ConnectorError::FailedToObtainAuthType + | errors::ConnectorError::FailedToObtainCertificate + | errors::ConnectorError::NoConnectorMetaData + | errors::ConnectorError::FailedToObtainCertificateKey + | errors::ConnectorError::NotSupported { .. } + | errors::ConnectorError::FlowNotSupported { .. } + | errors::ConnectorError::CaptureMethodNotSupported + | errors::ConnectorError::MissingConnectorMandateID + | errors::ConnectorError::MissingConnectorTransactionID + | errors::ConnectorError::MissingConnectorRefundID + | errors::ConnectorError::MissingApplePayTokenData + | errors::ConnectorError::WebhooksNotImplemented + | errors::ConnectorError::WebhookBodyDecodingFailed + | errors::ConnectorError::WebhookSignatureNotFound + | errors::ConnectorError::WebhookSourceVerificationFailed + | errors::ConnectorError::WebhookVerificationSecretNotFound + | errors::ConnectorError::WebhookVerificationSecretInvalid + | errors::ConnectorError::WebhookReferenceIdNotFound + | errors::ConnectorError::WebhookEventTypeNotFound + | errors::ConnectorError::WebhookResourceObjectNotFound + | errors::ConnectorError::WebhookResponseEncodingFailed + | errors::ConnectorError::InvalidDateFormat + | errors::ConnectorError::DateFormattingFailed + | errors::ConnectorError::InvalidDataFormat { .. } + | errors::ConnectorError::MismatchedPaymentData + | errors::ConnectorError::InvalidWalletToken + | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. } + | errors::ConnectorError::FileValidationFailed { .. } + | errors::ConnectorError::MissingConnectorRedirectionPayload { .. } + | errors::ConnectorError::FailedAtConnector { .. } + | errors::ConnectorError::MissingPaymentMethodType + | errors::ConnectorError::InSufficientBalanceInPaymentMethod + | errors::ConnectorError::RequestTimeoutReceived + | errors::ConnectorError::CurrencyNotSupported { .. } + | errors::ConnectorError::InvalidConnectorConfig { .. } => { + err.change_context(errors::ApiErrorResponse::RefundFailed { data: None }) + } }) } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 85a0ca5f2441..044e270a7ea9 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2459,8 +2459,7 @@ async fn get_bank_account_connector_details( })) } }, - None => Err(errors::ApiErrorResponse::InternalServerError.into()) - .attach_printable("Unable to fetch payment method data"), + None => Ok(None), } } diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index af10e91b5a08..b95c294466d3 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -174,6 +174,7 @@ pub async fn refresh_connector_auth( reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()), status_code: 504, attempt_status: None, + connector_transaction_id: None, }; Ok(Err(error_response)) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4d8daa1fe69d..f57c0640f1a8 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -601,19 +601,19 @@ pub fn validate_request_amount_and_amount_to_capture( } } -/// if confirm = true and capture method = automatic, amount_to_capture(if provided) must be equal to amount +/// if capture method = automatic, amount_to_capture(if provided) must be equal to amount #[instrument(skip_all)] pub fn validate_amount_to_capture_in_create_call_request( request: &api_models::payments::PaymentsRequest, ) -> CustomResult<(), errors::ApiErrorResponse> { - if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic - && request.confirm.unwrap_or(false) - { - if let Some((amount_to_capture, amount)) = request.amount_to_capture.zip(request.amount) { - let amount_int: i64 = amount.into(); - utils::when(amount_to_capture != amount_int, || { + if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic { + let total_capturable_amount = request.get_total_capturable_amount(); + if let Some((amount_to_capture, total_capturable_amount)) = + request.amount_to_capture.zip(total_capturable_amount) + { + utils::when(amount_to_capture != total_capturable_amount, || { Err(report!(errors::ApiErrorResponse::PreconditionFailed { - message: "amount_to_capture must be equal to amount when confirm = true and capture_method = automatic".into() + message: "amount_to_capture must be equal to total_capturable_amount when capture_method = automatic".into() })) }) } else { @@ -1693,7 +1693,8 @@ pub(crate) fn validate_status_with_capture_method( field_name: "payment.status".to_string(), current_flow: "captured".to_string(), current_value: status.to_string(), - states: "requires_capture, partially_captured, processing".to_string() + states: "requires_capture, partially_captured_and_capturable, processing" + .to_string() })) }, ) @@ -3685,3 +3686,22 @@ pub async fn get_gsm_record( }) .ok() } + +pub fn validate_order_details_amount( + order_details: Vec, + amount: i64, +) -> Result<(), errors::ApiErrorResponse> { + let total_order_details_amount: i64 = order_details + .iter() + .map(|order| order.amount * i64::from(order.quantity)) + .sum(); + + if total_order_details_amount != amount { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Total sum of order details doesn't match amount in payment request" + .to_string(), + }) + } else { + Ok(()) + } +} diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 125787e1a30f..28b6dbec96ab 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -102,6 +102,13 @@ impl utils::flatten_join_error(mandate_details_fut) )?; + if let Some(order_details) = &request.order_details { + helpers::validate_order_details_amount( + order_details.to_owned(), + payment_intent.amount, + )?; + } + helpers::validate_customer_access(&payment_intent, auth_flow, request)?; helpers::validate_payment_status_against_not_allowed_statuses( @@ -185,7 +192,7 @@ impl let shipping_address_fut = tokio::spawn( async move { - helpers::create_or_find_address_for_payment_by_request( + helpers::create_or_update_address_for_payment_by_request( store.as_ref(), m_request_shipping.as_ref(), m_payment_intent_shipping_address_id.as_deref(), @@ -213,7 +220,7 @@ impl let billing_address_fut = tokio::spawn( async move { - helpers::create_or_find_address_for_payment_by_request( + helpers::create_or_update_address_for_payment_by_request( store.as_ref(), m_request_billing.as_ref(), m_payment_intent_billing_address_id.as_deref(), @@ -693,6 +700,15 @@ impl let m_error_message = error_message.clone(); let m_db = state.clone().store; + let surcharge_amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.surcharge_amount); + let tax_amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.tax_on_surcharge_amount); + let payment_attempt_fut = tokio::spawn( async move { m_db.update_payment_attempt_with_attempt_id( @@ -716,6 +732,8 @@ impl amount_capturable: Some(authorized_amount), updated_by: storage_scheme.to_string(), merchant_connector_id, + surcharge_amount, + tax_amount, }, storage_scheme, ) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index ccf9fc3fad1c..c12f28e23390 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -186,6 +186,13 @@ impl payment_id: payment_id.clone(), })?; + if let Some(order_details) = &request.order_details { + helpers::validate_order_details_amount( + order_details.to_owned(), + payment_intent.amount, + )?; + } + payment_attempt = db .insert_payment_attempt(payment_attempt_new, storage_scheme) .await diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 3734abfc6ab5..2de5df38dba4 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -372,11 +372,10 @@ async fn payment_response_update_tracker( } else { None }, - surcharge_amount: router_data.request.get_surcharge_amount(), - tax_amount: router_data.request.get_tax_on_surcharge_amount(), updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), + connector_transaction_id: err.connector_transaction_id, }), ) } @@ -496,8 +495,6 @@ async fn payment_response_update_tracker( } else { None }, - surcharge_amount: router_data.request.get_surcharge_amount(), - tax_amount: router_data.request.get_tax_on_surcharge_amount(), updated_by: storage_scheme.to_string(), authentication_data, encoded_data, @@ -751,7 +748,7 @@ fn get_total_amount_captured( } None => { //Non multiple capture - let amount = request.get_capture_amount(); + let amount = request.get_capture_amount(payment_data); amount_captured.or_else(|| { if router_data_status == enums::AttemptStatus::Charged { amount diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 75d3b6b82b4c..1176eeb1dd3f 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -60,6 +60,13 @@ impl .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + if let Some(order_details) = &request.order_details { + helpers::validate_order_details_amount( + order_details.to_owned(), + payment_intent.amount, + )?; + } + payment_intent.setup_future_usage = request .setup_future_usage .or(payment_intent.setup_future_usage); diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index f16f7629578b..0fd45c5af3b5 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -382,8 +382,6 @@ where } else { None }, - surcharge_amount: None, - tax_amount: None, updated_by: storage_scheme.to_string(), authentication_data, encoded_data, @@ -410,11 +408,10 @@ where status: storage_enums::AttemptStatus::Failure, error_reason: Some(error_response.reason.clone()), amount_capturable: Some(0), - surcharge_amount: None, - tax_amount: None, updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), + connector_transaction_id: error_response.connector_transaction_id.clone(), }, storage_scheme, ) diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index daa39bc60989..5ea3520c4e8f 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -58,13 +58,12 @@ pub async fn refund_create_core( )?; // Amount is not passed in request refer from payment intent. - amount = req.amount.unwrap_or( - payment_intent - .amount_captured - .ok_or(errors::ApiErrorResponse::InternalServerError) - .into_report() - .attach_printable("amount captured is none in a successful payment")?, - ); + amount = req + .amount + .or(payment_intent.amount_captured) + .ok_or(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("amount captured is none in a successful payment")?; //[#299]: Can we change the flow based on some workflow idea utils::when(amount <= 0, || { @@ -190,15 +189,48 @@ pub async fn trigger_refund_to_gateway( types::RefundsData, types::RefundsResponseData, > = connector.connector.get_connector_integration(); - services::execute_connector_processing_step( + let router_data_res = services::execute_connector_processing_step( state, connector_integration, &router_data, payments::CallConnectorAction::Trigger, None, ) - .await - .to_refund_failed_response()? + .await; + let option_refund_error_update = + router_data_res + .as_ref() + .err() + .and_then(|error| match error.current_context() { + errors::ConnectorError::NotImplemented(message) => { + Some(storage::RefundUpdate::ErrorUpdate { + refund_status: Some(enums::RefundStatus::Failure), + refund_error_message: Some(message.to_string()), + refund_error_code: Some("NOT_IMPLEMENTED".to_string()), + updated_by: storage_scheme.to_string(), + }) + } + _ => None, + }); + // Update the refund status as failure if connector_error is NotImplemented + if let Some(refund_error_update) = option_refund_error_update { + state + .store + .update_refund( + refund.to_owned(), + refund_error_update, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed while updating refund: refund_id: {}", + refund.refund_id + ) + })?; + } + router_data_res.to_refund_failed_response()? } else { router_data }; @@ -889,7 +921,9 @@ pub async fn start_refund_workflow( ) -> Result<(), errors::ProcessTrackerError> { match refund_tracker.name.as_deref() { Some("EXECUTE_REFUND") => trigger_refund_execute_workflow(state, refund_tracker).await, - Some("SYNC_REFUND") => Box::pin(sync_refund_with_gateway_workflow(state, refund_tracker)).await, + Some("SYNC_REFUND") => { + Box::pin(sync_refund_with_gateway_workflow(state, refund_tracker)).await + } _ => Err(errors::ProcessTrackerError::JobNotFound), } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 710dc9281bfa..94cd482a2291 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1,12 +1,16 @@ use api_models::user as api; use diesel_models::enums::UserStatus; -use error_stack::IntoReport; +use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, Secret}; use router_env::env; use super::errors::{UserErrors, UserResponse}; use crate::{ - consts::user as consts, routes::AppState, services::ApplicationResponse, types::domain, + consts, + db::user::UserInterface, + routes::AppState, + services::{authentication::UserFromToken, ApplicationResponse}, + types::domain, }; pub async fn connect_account( @@ -79,3 +83,35 @@ pub async fn connect_account( Err(UserErrors::InternalServerError.into()) } } + +pub async fn change_password( + state: AppState, + request: api::ChangePasswordRequest, + user_from_token: UserFromToken, +) -> UserResponse<()> { + let user: domain::UserFromStorage = + UserInterface::find_user_by_id(&*state.store, &user_from_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + user.compare_password(request.old_password) + .change_context(UserErrors::InvalidOldPassword)?; + + let new_password_hash = + crate::utils::user::password::generate_password_hash(request.new_password)?; + + let _ = UserInterface::update_user_by_user_id( + &*state.store, + user.get_user_id(), + diesel_models::user::UserUpdate::AccountUpdate { + name: None, + password: Some(new_password_hash), + is_verified: None, + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index eef8cacc5f92..0586faabbf76 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{admin::*, api_locking}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api::admin, }; @@ -77,7 +77,10 @@ pub async fn retrieve_merchant_account( |state, _, req| get_merchant_account(state, req), auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantAccountRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -141,6 +144,7 @@ pub async fn update_merchant_account( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::MerchantAccountWrite, }, req.headers(), ), @@ -220,6 +224,7 @@ pub async fn payment_connector_create( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::MerchantConnectorAccountWrite, }, req.headers(), ), @@ -270,7 +275,10 @@ pub async fn payment_connector_retrieve( }, auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantConnectorAccountRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -312,7 +320,10 @@ pub async fn payment_connector_list( |state, _, merchant_id| list_payment_connectors(state, merchant_id), auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantConnectorAccountRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -359,6 +370,7 @@ pub async fn payment_connector_update( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::MerchantConnectorAccountWrite, }, req.headers(), ), @@ -407,7 +419,10 @@ pub async fn payment_connector_delete( |state, _, req| delete_payment_connector(state, req.merchant_id, req.merchant_connector_id), auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantConnectorAccountWrite, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -460,6 +475,7 @@ pub async fn business_profile_create( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::MerchantAccountWrite, }, req.headers(), ), @@ -484,7 +500,10 @@ pub async fn business_profile_retrieve( |state, _, profile_id| retrieve_business_profile(state, profile_id), auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantAccountRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -511,6 +530,7 @@ pub async fn business_profile_update( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::MerchantAccountWrite, }, req.headers(), ), @@ -555,7 +575,10 @@ pub async fn business_profiles_list( |state, _, merchant_id| list_business_profile(state, merchant_id), auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::MerchantAccountRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs index 7299aa696390..5b4c047b1466 100644 --- a/crates/router/src/routes/api_keys.rs +++ b/crates/router/src/routes/api_keys.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_keys, api_locking}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api as api_types, }; @@ -57,6 +57,7 @@ pub async fn api_key_create( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::ApiKeyWrite, }, req.headers(), ), @@ -101,6 +102,7 @@ pub async fn api_key_retrieve( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::ApiKeyRead, }, req.headers(), ), @@ -189,6 +191,7 @@ pub async fn api_key_revoke( &auth::AdminApiAuth, &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), + required_permission: Permission::ApiKeyWrite, }, req.headers(), ), @@ -237,7 +240,10 @@ pub async fn api_key_list( }, auth::auth_type( &auth::AdminApiAuth, - &auth::JWTAuthMerchantFromRoute { merchant_id }, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::ApiKeyRead, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index c5ab28ebcf6f..7f620241d303 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -153,10 +153,7 @@ impl AppState { }; #[cfg(feature = "olap")] - let pool = crate::analytics::AnalyticsProvider::from_conf( - &conf.analytics, - ) - .await; + let pool = crate::analytics::AnalyticsProvider::from_conf(&conf.analytics).await; #[cfg(feature = "kms")] #[allow(clippy::expect_used)] @@ -775,6 +772,7 @@ impl User { .service(web::resource("/signup").route(web::post().to(user_connect_account))) .service(web::resource("/v2/signin").route(web::post().to(user_connect_account))) .service(web::resource("/v2/signup").route(web::post().to(user_connect_account))) + .service(web::resource("/change_password").route(web::post().to(change_password))) } } diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs index aaeb118645db..7bcd8ad35124 100644 --- a/crates/router/src/routes/disputes.rs +++ b/crates/router/src/routes/disputes.rs @@ -3,7 +3,7 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::disputes as dispute_models; use router_env::{instrument, tracing, Flow}; -use crate::core::api_locking; +use crate::{core::api_locking, services::authorization::permissions::Permission}; pub mod utils; use super::app::AppState; @@ -44,7 +44,11 @@ pub async fn retrieve_dispute( &req, dispute_id, |state, auth, req| disputes::retrieve_dispute(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -87,7 +91,11 @@ pub async fn retrieve_disputes_list( &req, payload, |state, auth, req| disputes::retrieve_disputes_list(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -125,7 +133,11 @@ pub async fn accept_dispute( |state, auth, req| { disputes::accept_dispute(state, auth.merchant_account, auth.key_store, req) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -158,7 +170,11 @@ pub async fn submit_dispute_evidence( |state, auth, req| { disputes::submit_evidence(state, auth.merchant_account, auth.key_store, req) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -199,7 +215,11 @@ pub async fn attach_dispute_evidence( |state, auth, req| { disputes::attach_evidence(state, auth.merchant_account, auth.key_store, req) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -235,7 +255,11 @@ pub async fn retrieve_dispute_evidence( &req, dispute_id, |state, auth, req| disputes::retrieve_dispute_evidence(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::DisputeRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/files.rs b/crates/router/src/routes/files.rs index bde221ebc161..95f4007cb91b 100644 --- a/crates/router/src/routes/files.rs +++ b/crates/router/src/routes/files.rs @@ -2,7 +2,7 @@ use actix_multipart::Multipart; use actix_web::{web, HttpRequest, HttpResponse}; use router_env::{instrument, tracing, Flow}; -use crate::core::api_locking; +use crate::{core::api_locking, services::authorization::permissions::Permission}; pub mod transformers; use super::app::AppState; @@ -45,7 +45,11 @@ pub async fn files_create( &req, create_file_request, |state, auth, req| files_create_core(state, auth.merchant_account, auth.key_store, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::FileWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -83,7 +87,11 @@ pub async fn files_delete( &req, file_id, |state, auth, req| files_delete_core(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::FileWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -121,7 +129,11 @@ pub async fn files_retrieve( &req, file_id, |state, auth, req| files_retrieve_core(state, auth.merchant_account, auth.key_store, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::FileRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index a9cf7b44a73d..219948bdd4d2 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -144,7 +144,7 @@ impl From for ApiIdentifier { | Flow::GsmRuleUpdate | Flow::GsmRuleDelete => Self::Gsm, - Flow::UserConnectAccount => Self::User, + Flow::UserConnectAccount | Flow::ChangePassword => Self::User, } } } diff --git a/crates/router/src/routes/mandates.rs b/crates/router/src/routes/mandates.rs index 0213d48ddca7..1e4461362975 100644 --- a/crates/router/src/routes/mandates.rs +++ b/crates/router/src/routes/mandates.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, mandate}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api::mandates, }; @@ -122,7 +122,11 @@ pub async fn retrieve_mandates_list( &req, payload, |state, auth, req| mandate::retrieve_mandates_list(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MandateRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index 4c26ea71f7d5..d45d67568b89 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -118,7 +118,7 @@ pub async fn payments_link_list( &req, payload, |state, auth, payload| list_payment_link(state, auth.merchant_account, payload), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + &auth::ApiKeyAuth, api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 81e53ade5e96..979b15a3d7f2 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1,4 +1,7 @@ -use crate::core::api_locking::{self, GetLockingInput}; +use crate::{ + core::api_locking::{self, GetLockingInput}, + services::authorization::permissions::Permission, +}; pub mod helpers; use actix_web::{web, Responder}; @@ -128,7 +131,11 @@ pub async fn payments_create( }, match env::which() { env::Env::Production => &auth::ApiKeyAuth, - _ => auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + _ => auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentWrite), + req.headers(), + ), }, locking_action, )) @@ -262,7 +269,7 @@ pub async fn payments_retrieve( }, auth::auth_type( &*auth_type, - &auth::JWTAuth, + &auth::JWTAuth(Permission::PaymentRead), req.headers(), ), locking_action, @@ -843,7 +850,11 @@ pub async fn payments_list( &req, payload, |state, auth, req| payments::list_payments(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -863,7 +874,11 @@ pub async fn payments_list_by_filter( &req, payload, |state, auth, req| payments::apply_filters_on_payments(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -883,7 +898,11 @@ pub async fn get_filters_for_payments( &req, payload, |state, auth, req| payments::get_filters_for_payments(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index d370af6b8d7a..47e9f2bf42a8 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, refunds::*}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api::refunds, }; @@ -37,7 +37,11 @@ pub async fn refunds_create( &req, json_payload.into_inner(), |state, auth, req| refund_create_core(state, auth.merchant_account, auth.key_store, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RefundWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -88,7 +92,11 @@ pub async fn refunds_retrieve( refund_retrieve_core, ) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RefundRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -202,7 +210,11 @@ pub async fn refunds_list( &req, payload.into_inner(), |state, auth, req| refund_list(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RefundRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -235,7 +247,11 @@ pub async fn refunds_filter_list( &req, payload.into_inner(), |state, auth, req| refund_filter_list(state, auth.merchant_account, req), - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RefundRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 1d2549bb047a..e7e31cb36aeb 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -14,7 +14,7 @@ use router_env::{ use crate::{ core::{api_locking, conditional_config, routing, surcharge_decision_config}, routes::AppState, - services::{api as oss_api, authentication as auth}, + services::{api as oss_api, authentication as auth, authorization::permissions::Permission}, }; #[cfg(feature = "olap")] @@ -34,9 +34,13 @@ pub async fn routing_create_config( routing::create_routing_config(state, auth.merchant_account, auth.key_store, payload) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, )) .await @@ -65,9 +69,13 @@ pub async fn routing_link_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, )) .await @@ -91,9 +99,13 @@ pub async fn routing_retrieve_config( routing::retrieve_routing_config(state, auth.merchant_account, algorithm_id) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, )) .await @@ -122,9 +134,13 @@ pub async fn routing_retrieve_dictionary( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, )) .await @@ -142,9 +158,13 @@ pub async fn routing_retrieve_dictionary( routing::retrieve_merchant_routing_dictionary(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, )) .await @@ -172,9 +192,13 @@ pub async fn routing_unlink_config( routing::unlink_routing_config(state, auth.merchant_account, payload_req) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, )) .await @@ -192,9 +216,13 @@ pub async fn routing_unlink_config( routing::unlink_routing_config(state, auth.merchant_account, auth.key_store) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, )) .await @@ -217,9 +245,13 @@ pub async fn routing_update_default_config( routing::update_default_routing_config(state, auth.merchant_account, updated_config) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, ) .await @@ -240,9 +272,13 @@ pub async fn routing_retrieve_default_config( routing::retrieve_default_routing_config(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, ) .await @@ -270,9 +306,13 @@ pub async fn upsert_surcharge_decision_manager_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), api_locking::LockAction::NotApplicable, )) .await @@ -297,9 +337,13 @@ pub async fn delete_surcharge_decision_manager_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), api_locking::LockAction::NotApplicable, )) .await @@ -324,9 +368,13 @@ pub async fn retrieve_surcharge_decision_manager_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), api_locking::LockAction::NotApplicable, ) .await @@ -354,9 +402,13 @@ pub async fn upsert_decision_manager_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), api_locking::LockAction::NotApplicable, )) .await @@ -382,9 +434,13 @@ pub async fn delete_decision_manager_config( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), api_locking::LockAction::NotApplicable, )) .await @@ -406,9 +462,13 @@ pub async fn retrieve_decision_manager_config( conditional_config::retrieve_conditional_config(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), api_locking::LockAction::NotApplicable, ) .await @@ -434,9 +494,13 @@ pub async fn routing_retrieve_linked_config( routing::retrieve_linked_routing_config(state, auth.merchant_account, query_params) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, )) .await @@ -454,9 +518,13 @@ pub async fn routing_retrieve_linked_config( routing::retrieve_linked_routing_config(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingRead), api_locking::LockAction::NotApplicable, )) .await @@ -478,9 +546,17 @@ pub async fn routing_retrieve_default_config_for_profiles( routing::retrieve_default_routing_config_for_profiles(state, auth.merchant_account) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -512,9 +588,13 @@ pub async fn routing_update_default_config_for_profile( ) }, #[cfg(not(feature = "release"))] - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RoutingWrite), + req.headers(), + ), #[cfg(feature = "release")] - &auth::JWTAuth, + &auth::JWTAuth(Permission::RoutingWrite), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 0ff11ce087b5..7d3d183eda76 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -29,3 +29,21 @@ pub async fn user_connect_account( )) .await } + +pub async fn change_password( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ChangePassword; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, user, req| user::change_password(state, req, user), + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/verification.rs b/crates/router/src/routes/verification.rs index d0525bb272e8..4bcbacdf9912 100644 --- a/crates/router/src/routes/verification.rs +++ b/crates/router/src/routes/verification.rs @@ -5,7 +5,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, verification}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, }; #[instrument(skip_all, fields(flow = ?Flow::Verification))] @@ -32,7 +32,11 @@ pub async fn apple_pay_merchant_registration( merchant_id.clone(), ) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -60,7 +64,11 @@ pub async fn retrieve_apple_pay_verified_domains( mca_id.to_string(), ) }, - auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 0cd0313446a0..6f791681fb11 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -1,5 +1,6 @@ pub mod api; pub mod authentication; +pub mod authorization; pub mod encryption; #[cfg(feature = "olap")] pub mod jwt; diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index aae17195517d..5481d5c5cf9d 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -224,6 +224,7 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny errors::UserResult { + ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); let token_payload = Self { @@ -110,6 +113,14 @@ impl AuthToken { } } +#[derive(Clone)] +pub struct UserFromToken { + pub user_id: String, + pub merchant_id: String, + pub role_id: String, + pub org_id: String, +} + pub trait AuthInfo { fn get_merchant_id(&self) -> Option<&str>; } @@ -387,7 +398,7 @@ where } #[derive(Debug)] -pub(crate) struct JWTAuth; +pub(crate) struct JWTAuth(pub Permission); #[derive(serde::Deserialize)] struct JwtAuthPayloadFetchUnit { @@ -406,6 +417,10 @@ where state: &A, ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; + + let permissions = authorization::get_permissions(&payload.role_id)?; + authorization::check_authorization(&self.0, permissions)?; + Ok(( (), AuthenticationType::MerchantJWT { @@ -416,8 +431,37 @@ where } } +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuth +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserFromToken, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + Ok(( + UserFromToken { + user_id: payload.user_id.clone(), + merchant_id: payload.merchant_id.clone(), + org_id: payload.org_id, + role_id: payload.role_id, + }, + AuthenticationType::MerchantJWT { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + pub struct JWTAuthMerchantFromRoute { pub merchant_id: String, + pub required_permission: Permission, } #[async_trait] @@ -432,6 +476,9 @@ where ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; + let permissions = authorization::get_permissions(&payload.role_id)?; + authorization::check_authorization(&self.required_permission, permissions)?; + // Check if token has access to merchantID that has been requested through query param if payload.merchant_id != self.merchant_id { return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); @@ -460,6 +507,7 @@ where #[derive(serde::Deserialize)] struct JwtAuthPayloadFetchMerchantAccount { merchant_id: String, + role_id: String, } #[async_trait] @@ -475,6 +523,10 @@ where let payload = parse_jwt_payload::(request_headers, state) .await?; + + let permissions = authorization::get_permissions(&payload.role_id)?; + authorization::check_authorization(&self.0, permissions)?; + let key_store = state .store() .get_merchant_key_store_by_merchant_id( @@ -505,6 +557,53 @@ where } } +pub struct DashboardNoPermissionAuth; + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for DashboardNoPermissionAuth +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserFromToken, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + Ok(( + UserFromToken { + user_id: payload.user_id.clone(), + merchant_id: payload.merchant_id.clone(), + org_id: payload.org_id, + role_id: payload.role_id, + }, + AuthenticationType::MerchantJWT { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch<(), A> for DashboardNoPermissionAuth +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + parse_jwt_payload::(request_headers, state).await?; + + Ok(((), AuthenticationType::NoAuth)) + } +} + pub trait ClientSecretFetch { fn get_client_secret(&self) -> Option<&String>; } diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs new file mode 100644 index 000000000000..cad9b1ece62e --- /dev/null +++ b/crates/router/src/services/authorization.rs @@ -0,0 +1,27 @@ +use crate::core::errors::{ApiErrorResponse, RouterResult}; + +pub mod info; +pub mod permissions; +pub mod predefined_permissions; + +pub fn get_permissions(role: &str) -> RouterResult<&Vec> { + predefined_permissions::PREDEFINED_PERMISSIONS + .get(role) + .map(|role_info| role_info.get_permissions()) + .ok_or(ApiErrorResponse::InvalidJwtToken.into()) +} + +pub fn check_authorization( + required_permission: &permissions::Permission, + permissions: &[permissions::Permission], +) -> RouterResult<()> { + permissions + .contains(required_permission) + .then_some(()) + .ok_or( + ApiErrorResponse::AccessForbidden { + resource: required_permission.to_string(), + } + .into(), + ) +} diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs new file mode 100644 index 000000000000..c6b649f3de5c --- /dev/null +++ b/crates/router/src/services/authorization/info.rs @@ -0,0 +1,168 @@ +use strum::{EnumIter, IntoEnumIterator}; + +use super::permissions::Permission; + +pub fn get_authorization_info() -> Vec { + PermissionModule::iter() + .map(|module| ModuleInfo::new(&module)) + .collect() +} + +pub struct PermissionInfo { + pub enum_name: Permission, + pub description: &'static str, +} + +impl PermissionInfo { + pub fn new(permissions: &[Permission]) -> Vec { + let mut permission_infos = Vec::with_capacity(permissions.len()); + for permission in permissions { + if let Some(description) = Permission::get_permission_description(permission) { + permission_infos.push(Self { + enum_name: permission.clone(), + description, + }) + } + } + permission_infos + } +} + +#[derive(PartialEq, EnumIter, Clone)] +pub enum PermissionModule { + Payments, + Refunds, + MerchantAccount, + Connectors, + Forex, + Routing, + Analytics, + Mandates, + Disputes, + Files, + ThreeDsDecisionManager, + SurchargeDecisionManager, +} + +impl PermissionModule { + pub fn get_module_description(&self) -> &'static str { + match self { + Self::Payments => "Everything related to payments - like creating and viewing payment related information are within this module", + Self::Refunds => "Refunds module encompasses everything related to refunds - like creating and viewing payment related information", + Self::MerchantAccount => "Accounts module permissions allow the user to view and update account details, configure webhooks and much more", + Self::Connectors => "All connector related actions - like configuring new connectors, viewing and updating connector configuration lies with this module", + Self::Routing => "All actions related to new, active, and past routing stacks take place here", + Self::Forex => "Forex module permissions allow the user to view and query the forex rates", + Self::Analytics => "Permission to view and analyse the data relating to payments, refunds, sdk etc.", + Self::Mandates => "Everything related to mandates - like creating and viewing mandate related information are within this module", + Self::Disputes => "Everything related to disputes - like creating and viewing dispute related information are within this module", + Self::Files => "Permissions for uploading, deleting and viewing files for disputes", + Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", + Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant" + } + } +} + +pub struct ModuleInfo { + pub module: PermissionModule, + pub description: &'static str, + pub permissions: Vec, +} + +impl ModuleInfo { + pub fn new(module: &PermissionModule) -> Self { + let module_name = module.clone(); + let description = module.get_module_description(); + + match module { + PermissionModule::Payments => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::PaymentRead, + Permission::PaymentWrite, + ]), + }, + PermissionModule::Refunds => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::RefundRead, + Permission::RefundWrite, + ]), + }, + PermissionModule::MerchantAccount => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::MerchantAccountRead, + Permission::MerchantAccountWrite, + ]), + }, + PermissionModule::Connectors => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::MerchantConnectorAccountRead, + Permission::MerchantConnectorAccountWrite, + ]), + }, + PermissionModule::Forex => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[Permission::ForexRead]), + }, + PermissionModule::Routing => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::RoutingRead, + Permission::RoutingWrite, + ]), + }, + PermissionModule::Analytics => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[Permission::Analytics]), + }, + PermissionModule::Mandates => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::MandateRead, + Permission::MandateWrite, + ]), + }, + PermissionModule::Disputes => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::DisputeRead, + Permission::DisputeWrite, + ]), + }, + PermissionModule::Files => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[Permission::FileRead, Permission::FileWrite]), + }, + PermissionModule::ThreeDsDecisionManager => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::ThreeDsDecisionManagerWrite, + Permission::ThreeDsDecisionManagerRead, + ]), + }, + + PermissionModule::SurchargeDecisionManager => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::SurchargeDecisionManagerWrite, + Permission::SurchargeDecisionManagerRead, + ]), + }, + } + } +} diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs new file mode 100644 index 000000000000..708da97e1e39 --- /dev/null +++ b/crates/router/src/services/authorization/permissions.rs @@ -0,0 +1,74 @@ +use strum::Display; + +#[derive(PartialEq, Display, Clone, Debug)] +pub enum Permission { + PaymentRead, + PaymentWrite, + RefundRead, + RefundWrite, + ApiKeyRead, + ApiKeyWrite, + MerchantAccountRead, + MerchantAccountWrite, + MerchantConnectorAccountRead, + MerchantConnectorAccountWrite, + ForexRead, + RoutingRead, + RoutingWrite, + DisputeRead, + DisputeWrite, + MandateRead, + MandateWrite, + FileRead, + FileWrite, + Analytics, + ThreeDsDecisionManagerWrite, + ThreeDsDecisionManagerRead, + SurchargeDecisionManagerWrite, + SurchargeDecisionManagerRead, + UsersRead, + UsersWrite, + MerchantAccountCreate, +} + +impl Permission { + pub fn get_permission_description(&self) -> Option<&'static str> { + match self { + Self::PaymentRead => Some("View all payments"), + Self::PaymentWrite => Some("Create payment, download payments data"), + Self::RefundRead => Some("View all refunds"), + Self::RefundWrite => Some("Create refund, download refunds data"), + Self::ApiKeyRead => Some("View API keys (masked generated for the system"), + Self::ApiKeyWrite => Some("Create and update API keys"), + Self::MerchantAccountRead => Some("View merchant account details"), + Self::MerchantAccountWrite => { + Some("Update merchant account details, configure webhooks, manage api keys") + } + Self::MerchantConnectorAccountRead => Some("View connectors configured"), + Self::MerchantConnectorAccountWrite => { + Some("Create, update, verify and delete connector configurations") + } + Self::ForexRead => Some("Query Forex data"), + Self::RoutingRead => Some("View routing configuration"), + Self::RoutingWrite => Some("Create and activate routing configurations"), + Self::DisputeRead => Some("View disputes"), + Self::DisputeWrite => Some("Create and update disputes"), + Self::MandateRead => Some("View mandates"), + Self::MandateWrite => Some("Create and update mandates"), + Self::FileRead => Some("View files"), + Self::FileWrite => Some("Create, update and delete files"), + Self::Analytics => Some("Access to analytics module"), + Self::ThreeDsDecisionManagerWrite => Some("Create and update 3DS decision rules"), + Self::ThreeDsDecisionManagerRead => { + Some("View all 3DS decision rules configured for a merchant") + } + Self::SurchargeDecisionManagerWrite => { + Some("Create and update the surcharge decision rules") + } + Self::SurchargeDecisionManagerRead => Some("View all the surcharge decision rules"), + Self::UsersRead => Some("View all the users for a merchant"), + Self::UsersWrite => Some("Invite users, assign and update roles"), + Self::MerchantAccountCreate => None, + } + } +} diff --git a/crates/router/src/services/authorization/predefined_permissions.rs b/crates/router/src/services/authorization/predefined_permissions.rs new file mode 100644 index 000000000000..89fa2c8f739c --- /dev/null +++ b/crates/router/src/services/authorization/predefined_permissions.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +use super::permissions::Permission; +use crate::consts; + +pub struct RoleInfo { + permissions: Vec, + name: Option<&'static str>, + is_invitable: bool, +} + +impl RoleInfo { + pub fn get_permissions(&self) -> &Vec { + &self.permissions + } + + pub fn get_name(&self) -> Option<&'static str> { + self.name + } + + pub fn is_invitable(&self) -> bool { + self.is_invitable + } +} + +pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy::new(|| { + let mut roles = HashMap::new(); + roles.insert( + consts::ROLE_ID_ORGANIZATION_ADMIN, + RoleInfo { + permissions: vec![ + Permission::PaymentRead, + Permission::PaymentWrite, + Permission::RefundRead, + Permission::RefundWrite, + Permission::ApiKeyRead, + Permission::ApiKeyWrite, + Permission::MerchantAccountRead, + Permission::MerchantAccountWrite, + Permission::MerchantConnectorAccountRead, + Permission::MerchantConnectorAccountWrite, + Permission::RoutingRead, + Permission::RoutingWrite, + Permission::ForexRead, + Permission::ThreeDsDecisionManagerWrite, + Permission::ThreeDsDecisionManagerRead, + Permission::SurchargeDecisionManagerWrite, + Permission::SurchargeDecisionManagerRead, + Permission::DisputeRead, + Permission::DisputeWrite, + Permission::MandateRead, + Permission::MandateWrite, + Permission::FileRead, + Permission::FileWrite, + Permission::Analytics, + Permission::UsersRead, + Permission::UsersWrite, + Permission::MerchantAccountCreate, + ], + name: Some("Organization Admin"), + is_invitable: false, + }, + ); + roles +}); + +pub fn get_role_name_from_id(role_id: &str) -> Option<&'static str> { + PREDEFINED_PERMISSIONS + .get(role_id) + .and_then(|role_info| role_info.name) +} + +pub fn is_role_invitable(role_id: &str) -> bool { + PREDEFINED_PERMISSIONS + .get(role_id) + .map_or(false, |role_info| role_info.is_invitable) +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index a03e41650408..8c9d030965c9 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -30,9 +30,10 @@ use crate::core::utils::IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLO use crate::{ core::{ errors::{self, RouterResult}, - payments::RecurringMandatePaymentData, + payments::{PaymentData, RecurringMandatePaymentData}, }, services, + types::storage::payment_attempt::PaymentAttemptExt, utils::OptionExt, }; @@ -544,59 +545,66 @@ pub struct AccessTokenRequestData { } pub trait Capturable { - fn get_capture_amount(&self) -> Option { + fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + where + F: Clone, + { None } - fn get_surcharge_amount(&self) -> Option { - None - } - fn get_tax_on_surcharge_amount(&self) -> Option { - None - } - fn is_psync(&self) -> bool { - false - } } impl Capturable for PaymentsAuthorizeData { - fn get_capture_amount(&self) -> Option { + fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + where + F: Clone, + { let final_amount = self .surcharge_details .as_ref() .map(|surcharge_details| surcharge_details.final_amount); final_amount.or(Some(self.amount)) } - fn get_surcharge_amount(&self) -> Option { - self.surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.surcharge_amount) - } - fn get_tax_on_surcharge_amount(&self) -> Option { - self.surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.tax_on_surcharge_amount) - } } impl Capturable for PaymentsCaptureData { - fn get_capture_amount(&self) -> Option { + fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + where + F: Clone, + { Some(self.amount_to_capture) } } impl Capturable for CompleteAuthorizeData { - fn get_capture_amount(&self) -> Option { + fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + where + F: Clone, + { Some(self.amount) } } impl Capturable for SetupMandateRequestData {} -impl Capturable for PaymentsCancelData {} +impl Capturable for PaymentsCancelData { + fn get_capture_amount(&self, payment_data: &PaymentData) -> Option + where + F: Clone, + { + // return previously captured amount + payment_data.payment_intent.amount_captured + } +} impl Capturable for PaymentsApproveData {} impl Capturable for PaymentsRejectData {} impl Capturable for PaymentsSessionData {} impl Capturable for PaymentsSyncData { - fn is_psync(&self) -> bool { - true + fn get_capture_amount(&self, payment_data: &PaymentData) -> Option + where + F: Clone, + { + payment_data + .payment_attempt + .amount_to_capture + .or_else(|| Some(payment_data.payment_attempt.get_total_amount())) } } @@ -952,6 +960,7 @@ pub struct ErrorResponse { pub reason: Option, pub status_code: u16, pub attempt_status: Option, + pub connector_transaction_id: Option, } impl ErrorResponse { @@ -968,6 +977,7 @@ impl ErrorResponse { reason: None, status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), attempt_status: None, + connector_transaction_id: None, } } } @@ -1011,6 +1021,7 @@ impl From for ErrorResponse { _ => 500, }, attempt_status: None, + connector_transaction_id: None, } } } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index b7d2fc8db33e..bcb3a9add553 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -114,6 +114,7 @@ pub trait ConnectorCommon { message: consts::NO_ERROR_MESSAGE.to_string(), reason: None, attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 83586e51d66a..901e84997e67 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -405,6 +405,7 @@ pub fn handle_json_response_deserialization_failure( message: consts::UNSUPPORTED_ERROR_MESSAGE.to_string(), reason: Some(response_data), attempt_status: None, + connector_transaction_id: None, }) } } diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index c4b35cd6301a..f2760a00582d 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -135,11 +135,10 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { consts::REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC.to_string(), )), amount_capturable: Some(0), - surcharge_amount: None, - tax_amount: None, updated_by: merchant_account.storage_scheme.to_string(), unified_code: None, unified_message: None, + connector_transaction_id: None, }; payment_data.payment_attempt = db diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 178f837fce18..7978e98e52c0 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -255,6 +255,8 @@ pub enum Flow { DecisionManagerDeleteConfig, /// Retrieve Decision Manager Config DecisionManagerRetrieveConfig, + /// Change password flow + ChangePassword, } /// diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 238a2d75087c..06aacccc769d 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -553,7 +553,7 @@ impl PaymentAttemptInterface for KVRouterStore { } MerchantStorageScheme::RedisKv => { // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now - let lookup_id = format!("{merchant_id}_{connector_transaction_id}"); + let lookup_id = format!("conn_trans_{merchant_id}_{connector_transaction_id}"); let lookup = self .get_lookup_by_lookup_id(&lookup_id, storage_scheme) .await?; @@ -774,7 +774,7 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{preprocessing_id}"); + let lookup_id = format!("preprocessing_{merchant_id}_{preprocessing_id}"); let lookup = self .get_lookup_by_lookup_id(&lookup_id, storage_scheme) .await?; @@ -1215,6 +1215,8 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, amount_capturable, + surcharge_amount, + tax_amount, updated_by, merchant_connector_id: connector_id, } => DieselPaymentAttemptUpdate::ConfirmUpdate { @@ -1234,6 +1236,8 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, amount_capturable, + surcharge_amount, + tax_amount, updated_by, merchant_connector_id: connector_id, }, @@ -1261,8 +1265,6 @@ impl DataModelExt for PaymentAttemptUpdate { connector_response_reference_id, amount_capturable, updated_by, - surcharge_amount, - tax_amount, authentication_data, encoded_data, unified_code, @@ -1282,8 +1284,6 @@ impl DataModelExt for PaymentAttemptUpdate { connector_response_reference_id, amount_capturable, updated_by, - surcharge_amount, - tax_amount, authentication_data, encoded_data, unified_code, @@ -1320,11 +1320,10 @@ impl DataModelExt for PaymentAttemptUpdate { error_message, error_reason, amount_capturable, - tax_amount, - surcharge_amount, updated_by, unified_code, unified_message, + connector_transaction_id, } => DieselPaymentAttemptUpdate::ErrorUpdate { connector, status, @@ -1332,11 +1331,10 @@ impl DataModelExt for PaymentAttemptUpdate { error_message, error_reason, amount_capturable, - surcharge_amount, - tax_amount, updated_by, unified_code, unified_message, + connector_transaction_id, }, Self::CaptureUpdate { multiple_capture_count, @@ -1478,6 +1476,8 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, amount_capturable, + surcharge_amount, + tax_amount, updated_by, merchant_connector_id: connector_id, } => Self::ConfirmUpdate { @@ -1497,6 +1497,8 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, amount_capturable, + surcharge_amount, + tax_amount, updated_by, merchant_connector_id: connector_id, }, @@ -1524,8 +1526,6 @@ impl DataModelExt for PaymentAttemptUpdate { connector_response_reference_id, amount_capturable, updated_by, - surcharge_amount, - tax_amount, authentication_data, encoded_data, unified_code, @@ -1545,8 +1545,6 @@ impl DataModelExt for PaymentAttemptUpdate { connector_response_reference_id, amount_capturable, updated_by, - surcharge_amount, - tax_amount, authentication_data, encoded_data, unified_code, @@ -1583,11 +1581,10 @@ impl DataModelExt for PaymentAttemptUpdate { error_message, error_reason, amount_capturable, - surcharge_amount, - tax_amount, updated_by, unified_code, unified_message, + connector_transaction_id, } => Self::ErrorUpdate { connector, status, @@ -1596,10 +1593,9 @@ impl DataModelExt for PaymentAttemptUpdate { error_reason, amount_capturable, updated_by, - surcharge_amount, - tax_amount, unified_code, unified_message, + connector_transaction_id, }, DieselPaymentAttemptUpdate::CaptureUpdate { amount_to_capture, @@ -1675,7 +1671,7 @@ async fn add_connector_txn_id_to_reverse_lookup( ) -> CustomResult { let field = format!("pa_{}", updated_attempt_attempt_id); let reverse_lookup_new = ReverseLookupNew { - lookup_id: format!("{}_{}", merchant_id, connector_transaction_id), + lookup_id: format!("conn_trans_{}_{}", merchant_id, connector_transaction_id), pk_id: key.to_owned(), sk_id: field.clone(), source: "payment_attempt".to_string(), @@ -1697,7 +1693,7 @@ async fn add_preprocessing_id_to_reverse_lookup( ) -> CustomResult { let field = format!("pa_{}", updated_attempt_attempt_id); let reverse_lookup_new = ReverseLookupNew { - lookup_id: format!("{}_{}", merchant_id, preprocessing_id), + lookup_id: format!("preprocessing_{}_{}", merchant_id, preprocessing_id), pk_id: key.to_owned(), sk_id: field.clone(), source: "payment_attempt".to_string(), diff --git a/docker-compose-development.yml b/docker-compose-development.yml new file mode 100644 index 000000000000..500f397cfa30 --- /dev/null +++ b/docker-compose-development.yml @@ -0,0 +1,301 @@ +version: "3.8" + +volumes: + cargo_cache: + pg_data: + router_build_cache: + scheduler_build_cache: + drainer_build_cache: + redisinsight_store: + +networks: + router_net: + +services: + ### Dependencies + pg: + image: postgres:latest + ports: + - "5432:5432" + networks: + - router_net + volumes: + - pg_data:/VAR/LIB/POSTGRESQL/DATA + environment: + - POSTGRES_USER=db_user + - POSTGRES_PASSWORD=db_pass + - POSTGRES_DB=hyperswitch_db + + redis-standalone: + image: redis:7 + labels: + - redis + networks: + - router_net + ports: + - "6379" + + migration_runner: + image: rust:latest + command: "bash -c 'cargo install diesel_cli --no-default-features --features postgres && diesel migration --database-url postgres://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST}:$${DATABASE_PORT}/$${DATABASE_NAME} run'" + working_dir: /app + networks: + - router_net + volumes: + - ./:/app + environment: + - DATABASE_USER=db_user + - DATABASE_PASSWORD=db_pass + - DATABASE_HOST=pg + - DATABASE_PORT=5432 + - DATABASE_NAME=hyperswitch_db + + ### Application services + hyperswitch-server: + image: rust:latest + command: cargo run --bin router -- -f ./config/docker_compose.toml + working_dir: /app + ports: + - "8080:8080" + networks: + - router_net + volumes: + - ./:/app + - cargo_cache:/cargo_cache + - router_build_cache:/cargo_build_cache + environment: + - CARGO_HOME=/cargo_cache + - CARGO_TARGET_DIR=/cargo_build_cache + labels: + logs: "promtail" + healthcheck: + test: curl --fail http://localhost:8080/health || exit 1 + interval: 120s + retries: 4 + start_period: 20s + timeout: 10s + + hyperswitch-producer: + image: rust:latest + command: cargo run --bin scheduler -- -f ./config/docker_compose.toml + working_dir: /app + networks: + - router_net + profiles: + - scheduler + volumes: + - ./:/app + - cargo_cache:/cargo_cache + - scheduler_build_cache:/cargo_build_cache + environment: + - CARGO_HOME=/cargo_cache + - CARGO_TARGET_DIR=/cargo_build_cache + - SCHEDULER_FLOW=producer + depends_on: + hyperswitch-consumer: + condition: service_healthy + labels: + logs: "promtail" + + hyperswitch-consumer: + image: rust:latest + command: cargo run --bin scheduler -- -f ./config/docker_compose.toml + working_dir: /app + networks: + - router_net + profiles: + - scheduler + volumes: + - ./:/app + - cargo_cache:/cargo_cache + - scheduler_build_cache:/cargo_build_cache + environment: + - CARGO_HOME=/cargo_cache + - CARGO_TARGET_DIR=/cargo_build_cache + - SCHEDULER_FLOW=consumer + depends_on: + hyperswitch-server: + condition: service_started + labels: + logs: "promtail" + healthcheck: + test: (ps -e | grep scheduler) || exit 1 + interval: 120s + retries: 4 + start_period: 30s + timeout: 10s + + hyperswitch-drainer: + image: rust:latest + command: cargo run --bin drainer -- -f ./config/docker_compose.toml + working_dir: /app + deploy: + replicas: ${DRAINER_INSTANCE_COUNT:-1} + networks: + - router_net + profiles: + - full_kv + volumes: + - ./:/app + - cargo_cache:/cargo_cache + - drainer_build_cache:/cargo_build_cache + environment: + - CARGO_HOME=/cargo_cache + - CARGO_TARGET_DIR=/cargo_build_cache + restart: unless-stopped + depends_on: + hyperswitch-server: + condition: service_started + labels: + logs: "promtail" + + ### Clustered Redis setup + redis-cluster: + image: redis:7 + deploy: + replicas: ${REDIS_CLUSTER_COUNT:-3} + command: redis-server /usr/local/etc/redis/redis.conf + profiles: + - clustered_redis + volumes: + - ./config/redis.conf:/usr/local/etc/redis/redis.conf + labels: + - redis + networks: + - router_net + ports: + - "6379" + - "16379" + + redis-init: + image: redis:7 + profiles: + - clustered_redis + depends_on: + - redis-cluster + networks: + - router_net + command: "bash -c 'export COUNT=${REDIS_CLUSTER_COUNT:-3} + + \ if [ $$COUNT -lt 3 ] + + \ then + + \ echo \"Minimum 3 nodes are needed for redis cluster\" + + \ exit 1 + + \ fi + + \ HOSTS=\"\" + + \ for ((c=1; c<=$$COUNT;c++)) + + \ do + + \ NODE=$COMPOSE_PROJECT_NAME-redis-cluster-$$c:6379 + + \ echo $$NODE + + \ HOSTS=\"$$HOSTS $$NODE\" + + \ done + + \ echo Creating a cluster with $$HOSTS + + \ redis-cli --cluster create $$HOSTS --cluster-yes + + \ '" + + ### Monitoring + grafana: + image: grafana/grafana:latest + ports: + - "3000:3000" + networks: + - router_net + profiles: + - monitoring + restart: unless-stopped + environment: + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_BASIC_ENABLED=false + volumes: + - ./config/grafana.ini:/etc/grafana/grafana.ini + - ./config/grafana-datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yml + + promtail: + image: grafana/promtail:latest + volumes: + - ./logs:/var/log/router + - ./config:/etc/promtail + - /var/run/docker.sock:/var/run/docker.sock + command: -config.file=/etc/promtail/promtail.yaml + profiles: + - monitoring + networks: + - router_net + + loki: + image: grafana/loki:latest + ports: + - "3100" + command: -config.file=/etc/loki/loki.yaml + networks: + - router_net + profiles: + - monitoring + volumes: + - ./config:/etc/loki + + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + command: --config=/etc/otel-collector.yaml + networks: + - router_net + profiles: + - monitoring + volumes: + - ./config/otel-collector.yaml:/etc/otel-collector.yaml + ports: + - "4317" + - "8888" + - "8889" + + prometheus: + image: prom/prometheus:latest + networks: + - router_net + profiles: + - monitoring + volumes: + - ./config/prometheus.yaml:/etc/prometheus/prometheus.yml + ports: + - "9090" + restart: unless-stopped + + tempo: + image: grafana/tempo:latest + command: -config.file=/etc/tempo.yaml + volumes: + - ./config/tempo.yaml:/etc/tempo.yaml + networks: + - router_net + profiles: + - monitoring + ports: + - "3200" # tempo + - "4317" # otlp grpc + restart: unless-stopped + + redis-insight: + image: redislabs/redisinsight:latest + networks: + - router_net + profiles: + - full_kv + ports: + - "8001:8001" + volumes: + - redisinsight_store:/db diff --git a/docker-compose.yml b/docker-compose.yml index f4dce575132e..fd18906143f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,76 +1,16 @@ -version: "3.7" +version: "3.8" volumes: - cargo_cache: pg_data: - cargo_build_cache: - p_cargo_build_cache: - c_cargo_build_cache: redisinsight_store: - networks: router_net: - services: - promtail: - image: grafana/promtail:latest - volumes: - - ./logs:/var/log/router - - ./config:/etc/promtail - - /var/run/docker.sock:/var/run/docker.sock - command: -config.file=/etc/promtail/promtail.yaml - profiles: - - monitoring - networks: - - router_net - - loki: - image: grafana/loki:latest - ports: - - "3100" - command: -config.file=/etc/loki/loki.yaml - networks: - - router_net - profiles: - - monitoring - volumes: - - ./config:/etc/loki - - otel-collector: - image: otel/opentelemetry-collector-contrib:latest - command: --config=/etc/otel-collector.yaml - networks: - - router_net - profiles: - - monitoring - volumes: - - ./config/otel-collector.yaml:/etc/otel-collector.yaml - ports: - - "4317" - - "8888" - - "8889" - - grafana: - image: grafana/grafana:latest - ports: - - "3000:3000" - networks: - - router_net - profiles: - - monitoring - restart: unless-stopped - environment: - - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin - - GF_AUTH_ANONYMOUS_ENABLED=true - - GF_AUTH_BASIC_ENABLED=false - volumes: - - ./config/grafana.ini:/etc/grafana/grafana.ini - - ./config/grafana-datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yml - + ### Dependencies pg: - image: postgres:14.5 + image: postgres:latest ports: - "5432:5432" networks: @@ -82,52 +22,59 @@ services: - POSTGRES_PASSWORD=db_pass - POSTGRES_DB=hyperswitch_db + redis-standalone: + image: redis:7 + labels: + - redis + networks: + - router_net + ports: + - "6379" + migration_runner: - image: rust:1.70 - command: "bash -c 'cargo install diesel_cli --no-default-features --features \"postgres\" && diesel migration --database-url postgres://db_user:db_pass@pg:5432/hyperswitch_db run'" + image: rust:latest + command: "bash -c 'cargo install diesel_cli --no-default-features --features postgres && diesel migration --database-url postgres://$${DATABASE_USER}:$${DATABASE_PASSWORD}@$${DATABASE_HOST}:$${DATABASE_PORT}/$${DATABASE_NAME} run'" working_dir: /app networks: - router_net volumes: - ./:/app + environment: + - DATABASE_USER=db_user + - DATABASE_PASSWORD=db_pass + - DATABASE_HOST=pg + - DATABASE_PORT=5432 + - DATABASE_NAME=hyperswitch_db + ### Application services hyperswitch-server: - image: rust:1.70 - command: cargo run -- -f ./config/docker_compose.toml - working_dir: /app + image: juspaydotin/hyperswitch-router:standalone + command: /local/bin/router -f /local/config/docker_compose.toml ports: - "8080:8080" networks: - router_net volumes: - - ./:/app - - cargo_cache:/cargo_cache - - cargo_build_cache:/cargo_build_cache - environment: - - CARGO_TARGET_DIR=/cargo_build_cache + - ./config:/local/config labels: logs: "promtail" healthcheck: test: curl --fail http://localhost:8080/health || exit 1 - interval: 100s + interval: 10s retries: 3 - start_period: 20s + start_period: 5s timeout: 10s hyperswitch-producer: - image: rust:1.70 - command: cargo run --bin scheduler -- -f ./config/docker_compose.toml - working_dir: /app + image: juspaydotin/hyperswitch-producer:standalone + command: /local/bin/scheduler -f /local/config/docker_compose.toml networks: - router_net profiles: - scheduler volumes: - - ./:/app - - cargo_cache:/cargo_cache - - p_cargo_build_cache:/cargo_build_cache + - ./config:/local/config environment: - - CARGO_TARGET_DIR=/cargo_build_cache - SCHEDULER_FLOW=producer depends_on: hyperswitch-consumer: @@ -136,39 +83,54 @@ services: logs: "promtail" hyperswitch-consumer: - image: rust:1.70 - command: cargo run --bin scheduler -- -f ./config/docker_compose.toml - working_dir: /app + image: juspaydotin/hyperswitch-consumer:standalone + command: /local/bin/scheduler -f /local/config/docker_compose.toml networks: - router_net profiles: - scheduler volumes: - - ./:/app - - cargo_cache:/cargo_cache - - c_cargo_build_cache:/cargo_build_cache + - ./config:/local/config environment: - - CARGO_TARGET_DIR=/cargo_build_cache - SCHEDULER_FLOW=consumer depends_on: hyperswitch-server: condition: service_started - labels: logs: "promtail" - healthcheck: test: (ps -e | grep scheduler) || exit 1 - interval: 120s + interval: 10s retries: 3 - start_period: 30s + start_period: 5s timeout: 10s + hyperswitch-drainer: + image: juspaydotin/hyperswitch-drainer:standalone + command: /local/bin/drainer -f /local/config/docker_compose.toml + deploy: + replicas: ${DRAINER_INSTANCE_COUNT:-1} + networks: + - router_net + profiles: + - full_kv + volumes: + - ./config:/local/config + restart: unless-stopped + depends_on: + hyperswitch-server: + condition: service_started + labels: + logs: "promtail" + + ### Clustered Redis setup redis-cluster: image: redis:7 deploy: replicas: ${REDIS_CLUSTER_COUNT:-3} command: redis-server /usr/local/etc/redis/redis.conf + profiles: + - clustered_redis volumes: - ./config/redis.conf:/usr/local/etc/redis/redis.conf labels: @@ -179,17 +141,10 @@ services: - "6379" - "16379" - redis-standalone: - image: redis:7 - labels: - - redis - networks: - - router_net - ports: - - "6379" - redis-init: image: redis:7 + profiles: + - clustered_redis depends_on: - redis-cluster networks: @@ -226,16 +181,62 @@ services: \ '" - redis-insight: - image: redislabs/redisinsight:latest + ### Monitoring + grafana: + image: grafana/grafana:latest + ports: + - "3000:3000" networks: - router_net profiles: - - full_kv + - monitoring + restart: unless-stopped + environment: + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_BASIC_ENABLED=false + volumes: + - ./config/grafana.ini:/etc/grafana/grafana.ini + - ./config/grafana-datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yml + + promtail: + image: grafana/promtail:latest + volumes: + - ./logs:/var/log/router + - ./config:/etc/promtail + - /var/run/docker.sock:/var/run/docker.sock + command: -config.file=/etc/promtail/promtail.yaml + profiles: + - monitoring + networks: + - router_net + + loki: + image: grafana/loki:latest ports: - - "8001:8001" + - "3100" + command: -config.file=/etc/loki/loki.yaml + networks: + - router_net + profiles: + - monitoring volumes: - - redisinsight_store:/db + - ./config:/etc/loki + + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + command: --config=/etc/otel-collector.yaml + networks: + - router_net + profiles: + - monitoring + volumes: + - ./config/otel-collector.yaml:/etc/otel-collector.yaml + ports: + - "4317" + - "8888" + - "8889" + prometheus: image: prom/prometheus:latest networks: @@ -261,25 +262,14 @@ services: - "3200" # tempo - "4317" # otlp grpc restart: unless-stopped - hyperswitch-drainer: - image: rust:1.70 - command: cargo run --bin drainer -- -f ./config/docker_compose.toml - working_dir: /app - deploy: - replicas: ${DRAINER_INSTANCE_COUNT:-1} + + redis-insight: + image: redislabs/redisinsight:latest networks: - router_net profiles: - full_kv + ports: + - "8001:8001" volumes: - - ./:/app - - cargo_cache:/cargo_cache - - cargo_build_cache:/cargo_build_cache - environment: - - CARGO_TARGET_DIR=/cargo_build_cache - restart: unless-stopped - depends_on: - hyperswitch-server: - condition: service_started - labels: - logs: "promtail" + - redisinsight_store:/db diff --git a/docs/architecture.md b/docs/architecture.md index 3ab3b6a7eafa..24b0c726205a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -49,12 +49,7 @@ In addition to the database, Hyperswitch incorporates Redis for two main purpose ## Locker -The application utilizes a Locker, which consists of two distinct services: Temporary Locker and Permanent Locker. These services are responsible for securely storing payment-method information and adhere strictly to **Payment Card Industry Data Security Standard (PCI DSS)** compliance standards, ensuring that all payment-related data is handled and stored securely. - -- **Temporary Locker:** The Temporary Locker service handles the temporary storage of payment-method information. This temporary storage facilitates the smooth processing of transactions and reduces the exposure of sensitive information. -- **Permanent Locker:** The Permanent Locker service is responsible for the long-term storage of payment-method related data. It securely stores card details, such as cardholder information or payment method details, for future reference or recurring payments. - -> Currently, Locker service is not part of open-source +The application utilizes a Rust locker built with a GDPR compliant PII (personal identifiable information) storage. It also uses secure encryption algorithms to be fully compliant with **PCI DSS** (Payment Card Industry Data Security Standard) requirements, this ensures that all payment-related data is handled and stored securely. You can find the source code of locker [here](https://github.com/juspay/hyperswitch-card-vault). ## Monitoring diff --git a/docs/imgs/hyperswitch-architecture.png b/docs/imgs/hyperswitch-architecture.png index 18f42f9a55c5..f73f60f3e35e 100644 Binary files a/docs/imgs/hyperswitch-architecture.png and b/docs/imgs/hyperswitch-architecture.png differ diff --git a/docs/try_local_system.md b/docs/try_local_system.md index 59df43f24810..a9cd080f26d5 100644 --- a/docs/try_local_system.md +++ b/docs/try_local_system.md @@ -1,23 +1,20 @@ # Try out hyperswitch on your system -**NOTE:** -This guide is aimed at users and developers who wish to set up hyperswitch on -their local systems and requires quite some time and effort. -If you'd prefer trying out hyperswitch quickly without the hassle of setting up -all dependencies, you can [try out hyperswitch sandbox environment][try-sandbox]. - -There are two options to set up hyperswitch on your system: - -1. Use Docker Compose -2. Set up a Rust environment and other dependencies on your system +The simplest way to run hyperswitch locally is +[with Docker Compose](#run-hyperswitch-using-docker-compose) by pulling the +latest images from Docker Hub. +However, if you're willing to modify the code and run it, or are a developer +contributing to hyperswitch, then you can either +[set up a development environment using Docker Compose](#set-up-a-development-environment-using-docker-compose), +or [set up a Rust environment on your system](#set-up-a-rust-environment-and-other-dependencies). Check the Table Of Contents to jump to the relevant section. -[try-sandbox]: ./try_sandbox.md - **Table Of Contents:** -- [Set up hyperswitch using Docker Compose](#set-up-hyperswitch-using-docker-compose) +- [Run hyperswitch using Docker Compose](#run-hyperswitch-using-docker-compose) + - [Run the scheduler and monitoring services](#run-the-scheduler-and-monitoring-services) +- [Set up a development environment using Docker Compose](#set-up-a-development-environment-using-docker-compose) - [Set up a Rust environment and other dependencies](#set-up-a-rust-environment-and-other-dependencies) - [Set up dependencies on Ubuntu-based systems](#set-up-dependencies-on-ubuntu-based-systems) - [Set up dependencies on Windows (Ubuntu on WSL2)](#set-up-dependencies-on-windows-ubuntu-on-wsl2) @@ -33,7 +30,7 @@ Check the Table Of Contents to jump to the relevant section. - [Create a Payment](#create-a-payment) - [Create a Refund](#create-a-refund) -## Set up hyperswitch using Docker Compose +## Run hyperswitch using Docker Compose 1. Install [Docker Compose][docker-compose-install]. 2. Clone the repository and switch to the project directory: @@ -54,15 +51,15 @@ Check the Table Of Contents to jump to the relevant section. docker compose up -d ``` -5. Run database migrations: - - ```shell - docker compose run hyperswitch-server bash -c \ - "cargo install diesel_cli && \ - diesel migration --database-url postgres://db_user:db_pass@pg:5432/hyperswitch_db run" - ``` + This should run the hyperswitch payments router, the primary component within + hyperswitch. + Wait for the `migration_runner` container to finish installing `diesel_cli` + and running migrations (approximately 2 minutes) before proceeding further. + You can also choose to + [run the scheduler and monitoring services](#run-the-scheduler-and-monitoring-services) + in addition to the payments router. -6. Verify that the server is up and running by hitting the health endpoint: +5. Verify that the server is up and running by hitting the health endpoint: ```shell curl --head --request GET 'http://localhost:8080/health' @@ -71,9 +68,86 @@ Check the Table Of Contents to jump to the relevant section. If the command returned a `200 OK` status code, proceed with [trying out our APIs](#try-out-our-apis). +### Run the scheduler and monitoring services + +You can run the scheduler and monitoring services by specifying suitable profile +names to the above Docker Compose command. +To understand more about the hyperswitch architecture and the components +involved, check out the [architecture document][architecture]. + +- To run the scheduler components (consumer and producer), you can specify + `--profile scheduler`: + + ```shell + docker compose --profile scheduler up -d + ``` + +- To run the monitoring services (Grafana, Promtail, Loki, Prometheus and Tempo), + you can specify `--profile monitoring`: + + ```shell + docker compose --profile monitoring up -d + ``` + + You can then access Grafana at `http://localhost:3000` and view application + logs using the "Explore" tab, select Loki as the data source, and select the + container to query logs from. + +- You can also specify multiple profile names by specifying the `--profile` flag + multiple times. + To run both the scheduler components and monitoring services, the Docker + Compose command would be: + + ```shell + docker compose --profile scheduler --profile monitoring up -d + ``` + +Once the services have been confirmed to be up and running, you can proceed with +[trying out our APIs](#try-out-our-apis) + [docker-compose-install]: https://docs.docker.com/compose/install/ [docker-compose-config]: /config/docker_compose.toml [docker-compose-yml]: /docker-compose.yml +[architecture]: /docs/architecture.md + +## Set up a development environment using Docker Compose + +1. Install [Docker Compose][docker-compose-install]. +2. Clone the repository and switch to the project directory: + + ```shell + git clone https://github.com/juspay/hyperswitch + cd hyperswitch + ``` + +3. (Optional) Configure the application using the + [`config/docker_compose.toml`][docker-compose-config] file. + The provided configuration should work as is. + If you do update the `docker_compose.toml` file, ensure to also update the + corresponding values in the [`docker-compose.yml`][docker-compose-yml] file. +4. Start all the services using Docker Compose: + + ```shell + docker compose --file docker-compose-development.yml up -d + ``` + + This will compile the payments router, the primary component within + hyperswitch and then start it. + Depending on the specifications of your machine, compilation can take + around 15 minutes. + +5. (Optional) You can also choose to + [start the scheduler and/or monitoring services](#run-the-scheduler-and-monitoring-services) + in addition to the payments router. + +6. Verify that the server is up and running by hitting the health endpoint: + + ```shell + curl --head --request GET 'http://localhost:8080/health' + ``` + + If the command returned a `200 OK` status code, proceed with + [trying out our APIs](#try-out-our-apis). ## Set up a Rust environment and other dependencies @@ -134,7 +208,7 @@ for your distribution and follow along. 4. Install `diesel_cli` using `cargo`: ```shell - cargo install diesel_cli --no-default-features --features "postgres" + cargo install diesel_cli --no-default-features --features postgres ``` 5. Make sure your system has the `pkg-config` package and OpenSSL installed: @@ -224,7 +298,7 @@ packages for your distribution and follow along. 6. Install `diesel_cli` using `cargo`: ```shell - cargo install diesel_cli --no-default-features --features "postgres" + cargo install diesel_cli --no-default-features --features postgres ``` 7. Make sure your system has the `pkg-config` package and OpenSSL installed: @@ -260,7 +334,7 @@ You can opt to use your favorite package manager instead. 4. Install `diesel_cli` using `cargo`: ```shell - cargo install diesel_cli --no-default-features --features "postgres" + cargo install diesel_cli --no-default-features --features postgres ``` 5. Install OpenSSL with `winget`: @@ -322,7 +396,7 @@ You can opt to use your favorite package manager instead. 4. Install `diesel_cli` using `cargo`: ```shell - cargo install diesel_cli --no-default-features --features "postgres" + cargo install diesel_cli --no-default-features --features postgres ``` If linking `diesel_cli` fails due to missing `libpq` (if the error message is @@ -333,7 +407,7 @@ You can opt to use your favorite package manager instead. brew install libpq export PQ_LIB_DIR="$(brew --prefix libpq)/lib" - cargo install diesel_cli --no-default-features --features "postgres" + cargo install diesel_cli --no-default-features --features postgres ``` You may also choose to persist the value of `PQ_LIB_DIR` in your shell diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 056601ac707d..88a0d115ff01 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -5372,13 +5372,13 @@ { "type": "string", "enum": [ - "user_addressline1" + "user_address_line1" ] }, { "type": "string", "enum": [ - "user_addressline2" + "user_address_line2" ] }, { diff --git a/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Confirm/request.json b/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Confirm/request.json index 8559af25e82c..91426564e8e1 100644 --- a/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Confirm/request.json +++ b/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Confirm/request.json @@ -39,10 +39,6 @@ }, "raw_json_formatted": { "client_secret": "{{client_secret}}", - "surcharge_details": { - "surcharge_amount": 5, - "tax_amount": 5 - }, "payment_method": "card", "payment_method_data": { "card": { diff --git a/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Create/request.json b/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Create/request.json index f7d813c34efd..9e084a35c8c9 100644 --- a/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Create/request.json +++ b/postman/collection-dir/paypal/Flow Testcases/Happy Cases/Scenario8-Create payment with Manual capture with confirm false and surcharge_data/Payments - Create/request.json @@ -31,6 +31,10 @@ "description": "Its my first payment request", "authentication_type": "no_three_ds", "return_url": "https://duck.com", + "surcharge_details": { + "surcharge_amount": 5, + "tax_amount": 5 + }, "billing": { "address": { "line1": "1467", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario12-BNPL-klarna/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario12-BNPL-klarna/Payments - Create/request.json index b0bc12a6ac89..f621bd52f00d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario12-BNPL-klarna/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario12-BNPL-klarna/Payments - Create/request.json @@ -23,7 +23,7 @@ "confirm": false, "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 6540, + "amount_to_capture": 8000, "customer_id": "StripeCustomer", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-json/paypal.postman_collection.json b/postman/collection-json/paypal.postman_collection.json index d9deae47f9af..a6ee545a9497 100644 --- a/postman/collection-json/paypal.postman_collection.json +++ b/postman/collection-json/paypal.postman_collection.json @@ -808,7 +808,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"paypal\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"surcharge_details\":{\"surcharge_amount\":5,\"tax_amount\":5},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"paypal\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -990,7 +990,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"surcharge_details\":{\"surcharge_amount\":5,\"tax_amount\":5},\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4012000033330026\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4012000033330026\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index 9bdb5fdb44d9..4d3e548f535f 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -14445,7 +14445,7 @@ "language": "json" } }, - "raw": "{\"amount\":8000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":8000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":8000,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 9fdc57bf3c81..9a30fe9d7573 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -45,7 +45,7 @@ cd $SCRIPT/.. # Remove template files if already created for this connector rm -rf $conn/$payment_gateway $conn/$payment_gateway.rs -git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml crates/api_models/src/enums.rs $src/core/payments/flows.rs +git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml crates/api_models/src/enums.rs crates/euclid/src/enums.rs crates/api_models/src/routing.rs $src/core/payments/flows.rs $src/core/admin.rs $src/core/payments/routing/transformers.rs $src/types/transformers.rs # Add enum for this connector in required places previous_connector='' @@ -54,15 +54,21 @@ previous_connector_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${previous_connec sed -i'' -e "s|pub mod $previous_connector;|pub mod $previous_connector;\npub mod ${payment_gateway};|" $conn.rs sed -i'' -e "s/};/${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tenums::Connector::${payment_gateway_camelcase} => Ok(Box::new(\&connector::${payment_gateway_camelcase})),|" $src/types/api.rs +sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tRoutableConnectors::${payment_gateway_camelcase} => euclid_enums::Connector::${payment_gateway_camelcase},|" crates/api_models/src/routing.rs sed -i'' -e "s/pub $previous_connector: \(.*\)/pub $previous_connector: \1\n\tpub ${payment_gateway}: ConnectorParams,/" $src/configs/settings.rs sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -r -i'' -e "s/\"$previous_connector\",/\"$previous_connector\",\n \"${payment_gateway}\",/" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/enums.rs +sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/euclid/src/enums.rs +sed -i '' -e "s/\(match connector_name {\)/\1\n\t\tapi_enums::Connector::${payment_gateway_camelcase} => {${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(val)?;Ok(())}/" $src/core/admin.rs +sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tapi_enums::RoutableConnectors::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/core/payments/routing/transformers.rs +sed -i'' -e "s|dsl_enums::Connector::$previous_connector_camelcase \(.*\)|dsl_enums::Connector::$previous_connector_camelcase \1\n\t\t\tdsl_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs +sed -i'' -e "s|api_enums::Connector::$previous_connector_camelcase \(.*\)|api_enums::Connector::$previous_connector_camelcase \1\n\t\t\tapi_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs sed -i'' -e "s/\(pub enum RoutableConnectors {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/enums.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnector::${payment_gateway_camelcase},/" $src/core/payments/flows.rs # Remove temporary files created in above step -rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/enums.rs-e $src/core/payments/flows.rs-e +rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e $src/core/admin.rs-e $src/core/payments/routing/transformers.rs-e $src/types/transformers.rs-e cd $conn/ # Generate template files for the connector