diff --git a/.cargo/config.toml b/.cargo/config.toml index 3082e9635cf..5b27955262a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,9 +3,13 @@ rustflags = [ "-Funsafe_code", "-Wclippy::as_conversions", "-Wclippy::expect_used", + "-Wclippy::index_refutable_slice", + "-Wclippy::indexing_slicing", + "-Wclippy::match_on_vec_items", "-Wclippy::missing_panics_doc", - "-Wclippy::panic_in_result_fn", + "-Wclippy::out_of_bounds_indexing", "-Wclippy::panic", + "-Wclippy::panic_in_result_fn", "-Wclippy::panicking_unwrap", "-Wclippy::todo", "-Wclippy::unimplemented", @@ -23,10 +27,7 @@ rustflags = [ [build] -rustdocflags = [ - "--cfg", - "uuid_unstable" -] +rustdocflags = ["--cfg", "uuid_unstable"] [alias] gen-pg = "generate --path ../../../../connector-template -n" diff --git a/.dockerignore b/.dockerignore index 62804a712fa..81ef10ad213 100644 --- a/.dockerignore +++ b/.dockerignore @@ -261,7 +261,3 @@ result* # node_modules node_modules/ - -**/connector_auth.toml -**/sample_auth.toml -**/auth.toml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a911d26d865..0eb3d95bfc6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,17 @@ postman/ @juspay/hyperswitch-framework Cargo.toml @juspay/hyperswitch-framework Cargo.lock @juspay/hyperswitch-framework +crates/api_models/src/events/ @juspay/hyperswitch-analytics +crates/api_models/src/events.rs @juspay/hyperswitch-analytics +crates/api_models/src/analytics/ @juspay/hyperswitch-analytics +crates/api_models/src/analytics.rs @juspay/hyperswitch-analytics +crates/router/src/analytics.rs @juspay/hyperswitch-analytics +crates/router/src/events/ @juspay/hyperswitch-analytics +crates/router/src/events.rs @juspay/hyperswitch-analytics +crates/common_utils/src/events/ @juspay/hyperswitch-analytics +crates/common_utils/src/events.rs @juspay/hyperswitch-analytics +crates/analytics/ @juspay/hyperswitch-analytics + connector-template/ @juspay/hyperswitch-connector crates/router/src/connector/ @juspay/hyperswitch-connector crates/router/tests/connectors/ @juspay/hyperswitch-connector diff --git a/.github/git-cliff-changelog.toml b/.github/git-cliff-changelog.toml index 1d7e4080cc2..f9959eebfc6 100644 --- a/.github/git-cliff-changelog.toml +++ b/.github/git-cliff-changelog.toml @@ -14,7 +14,7 @@ body = """ {% set commit_base_url = "https://github.com/juspay/hyperswitch/commit/" -%} {% set compare_base_url = "https://github.com/juspay/hyperswitch/compare/" -%} {% if version -%} - ## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }}) + ## {{ version }} {% else -%} ## [unreleased] {% endif -%} @@ -69,7 +69,8 @@ commit_parsers = [ { message = "^(?i)(refactor)", group = "Refactors" }, { message = "^(?i)(test)", group = "Testing" }, { message = "^(?i)(docs)", group = "Documentation" }, - { message = "^(?i)(chore\\(version\\)): V[\\d]+\\.[\\d]+\\.[\\d]+", skip = true }, + { message = "^(?i)(chore\\(version\\)): (V|v)[\\d]+\\.[\\d]+\\.[\\d]+", skip = true }, + { message = "^(?i)(chore\\(version\\)): [0-9]{4}\\.[0-9]{2}\\.[0-9]{2}(\\.[0-9]+)?(-.+)?", skip = true }, { message = "^(?i)(chore)", group = "Miscellaneous Tasks" }, { message = "^(?i)(build)", group = "Build System / Dependencies" }, { message = "^(?i)(ci)", skip = true }, @@ -79,7 +80,7 @@ protect_breaking_commits = false # filter out the commits that are not matched by commit parsers filter_commits = false # glob pattern for matching git tags -tag_pattern = "v[0-9]*" +tag_pattern = "[0-9]{4}\\.[0-9]{2}\\.[0-9]{2}(\\.[0-9]+)?(-.+)?" # regex for skipping tags # skip_tags = "v0.1.0-beta.1" # regex for ignoring tags diff --git a/.github/secrets/connector_auth.toml.gpg b/.github/secrets/connector_auth.toml.gpg deleted file mode 100644 index 7da9189ade5..00000000000 Binary files a/.github/secrets/connector_auth.toml.gpg and /dev/null differ diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index 79cb352acbb..d6b3d98b8c8 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -130,159 +130,53 @@ jobs: shell: bash run: sed -i 's/rustflags = \[/rustflags = \[\n "-Dwarnings",/' .cargo/config.toml - - name: Check files changed + - name: Check modified crates shell: bash run: | - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/api_models/; then - echo "api_models_changes_exist=false" >> $GITHUB_ENV - else - echo "api_models_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/cards/; then - echo "cards_changes_exist=false" >> $GITHUB_ENV - else - echo "cards_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/common_enums/; then - echo "common_enums_changes_exist=false" >> $GITHUB_ENV - else - echo "common_enums_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/common_utils/; then - echo "common_utils_changes_exist=false" >> $GITHUB_ENV - else - echo "common_utils_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/diesel_models/; then - echo "diesel_models_changes_exist=false" >> $GITHUB_ENV - else - echo "diesel_models_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/drainer/; then - echo "drainer_changes_exist=false" >> $GITHUB_ENV - else - echo "drainer_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/external_services/; then - echo "external_services_changes_exist=false" >> $GITHUB_ENV - else - echo "external_services_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/masking/; then - echo "masking_changes_exist=false" >> $GITHUB_ENV - else - echo "masking_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/redis_interface/; then - echo "redis_interface_changes_exist=false" >> $GITHUB_ENV - else - echo "redis_interface_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router/; then - echo "router_changes_exist=false" >> $GITHUB_ENV - else - echo "router_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/storage_impl/; then - echo "storage_impl_changes_exist=false" >> $GITHUB_ENV - else - echo "storage_impl_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router_derive/; then - echo "router_derive_changes_exist=false" >> $GITHUB_ENV - else - echo "router_derive_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router_env/; then - echo "router_env_changes_exist=false" >> $GITHUB_ENV - else - echo "router_env_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/test_utils/; then - echo "test_utils_changes_exist=false" >> $GITHUB_ENV - else - echo "test_utils_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then - echo "pm_auth_changes_exist=false" >> $GITHUB_ENV - else - echo "pm_auth_changes_exist=true" >> $GITHUB_ENV - fi - - - name: Cargo hack api_models - if: env.api_models_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p api_models - - - name: Cargo hack cards - if: env.cards_changes_exist == 'true' + # Obtain a list of workspace members + workspace_members="$( + cargo metadata --format-version 1 --no-deps \ + | jq --compact-output --monochrome-output --raw-output '.workspace_members | sort | .[] | split(" ")[0]' + )" + + PACKAGES_CHECKED=() + PACKAGES_SKIPPED=() + + while IFS= read -r package_name; do + # Obtain comma-separated list of transitive workspace dependencies for each workspace member + change_paths="$(cargo tree --all-features --no-dedupe --prefix none --package "${package_name}" \ + | grep 'crates/' \ + | sort --unique \ + | awk --field-separator ' ' '{ printf "crates/%s\n", $1 }' | paste -d ',' -s -)" + + # Store change paths in an array by splitting `change_paths` by comma + IFS=',' read -ra change_paths <<< "${change_paths}" + + # A package must be checked if any of its transitive dependencies (or itself) has been modified + if git diff --exit-code --quiet "origin/${GITHUB_BASE_REF}" -- "${change_paths[@]}"; then + printf '::debug::Skipping `%s` since none of these paths were modified: %s\n' "${package_name}" "${change_paths[*]}" + PACKAGES_SKIPPED+=("${package_name}") + else + printf '::debug::Checking `%s` since at least one of these paths was modified: %s\n' "${package_name}" "${change_paths[*]}" + PACKAGES_CHECKED+=("${package_name}") + fi + done <<< "${workspace_members}" + + printf '::notice::Packages checked: %s; Packages skipped: %s\n' "${PACKAGES_CHECKED[*]}" "${PACKAGES_SKIPPED[*]}" + echo "PACKAGES_CHECKED=${PACKAGES_CHECKED[*]}" >> ${GITHUB_ENV} + echo "PACKAGES_SKIPPED=${PACKAGES_SKIPPED[*]}" >> ${GITHUB_ENV} + + - name: Run `cargo hack` on modified crates shell: bash - run: cargo hack check --each-feature --no-dev-deps -p cards - - - name: Cargo hack common_enums - if: env.common_enums_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p common_enums - - - name: Cargo hack common_utils - if: env.common_utils_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p common_utils - - - name: Cargo hack diesel_models - if: env.diesel_models_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p diesel_models - - - name: Cargo hack drainer - if: env.drainer_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p drainer - - - name: Cargo hack external_services - if: env.external_services_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p external_services - - - name: Cargo hack masking - if: env.masking_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p masking - - - name: Cargo hack redis_interface - if: env.redis_interface_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p redis_interface - - - name: Cargo hack pm_auth - if: env.pm_auth_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p pm_auth - - - name: Cargo hack router - if: env.router_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --skip kms,basilisk,kv_store,accounts_cache,openapi --no-dev-deps -p router - - - name: Cargo hack storage_impl - if: env.storage_impl_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p storage_impl - - - name: Cargo hack router_derive - if: env.router_derive_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p router_derive + run: | + # Store packages to check in an array by splitting `PACKAGES_CHECKED` by space + IFS=' ' read -ra PACKAGES_CHECKED <<< "${PACKAGES_CHECKED}" - - name: Cargo hack router_env - if: env.router_env_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p router_env - - - name: Cargo hack test_utils - if: env.test_utils_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p test_utils + for package in "${PACKAGES_CHECKED[@]}"; do + printf '::group::Running `cargo hack` on package `%s`\n' "${package}" + cargo hack check --each-feature --all-targets --package "${package}" + echo '::endgroup::' + done # cargo-deny: # name: Run cargo-deny @@ -393,159 +287,53 @@ jobs: git push fi - - name: Check files changed + - name: Check modified crates shell: bash run: | - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/api_models/; then - echo "api_models_changes_exist=false" >> $GITHUB_ENV - else - echo "api_models_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/cards/; then - echo "cards_changes_exist=false" >> $GITHUB_ENV - else - echo "cards_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/common_enums/; then - echo "common_enums_changes_exist=false" >> $GITHUB_ENV - else - echo "common_enums_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/common_utils/; then - echo "common_utils_changes_exist=false" >> $GITHUB_ENV - else - echo "common_utils_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/diesel_models/; then - echo "diesel_models_changes_exist=false" >> $GITHUB_ENV - else - echo "diesel_models_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/drainer/; then - echo "drainer_changes_exist=false" >> $GITHUB_ENV - else - echo "drainer_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/external_services/; then - echo "external_services_changes_exist=false" >> $GITHUB_ENV - else - echo "external_services_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/masking/; then - echo "masking_changes_exist=false" >> $GITHUB_ENV - else - echo "masking_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/redis_interface/; then - echo "redis_interface_changes_exist=false" >> $GITHUB_ENV - else - echo "redis_interface_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router/; then - echo "router_changes_exist=false" >> $GITHUB_ENV - else - echo "router_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router_derive/; then - echo "router_derive_changes_exist=false" >> $GITHUB_ENV - else - echo "router_derive_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/storage_impl/; then - echo "storage_impl_changes_exist=false" >> $GITHUB_ENV - else - echo "storage_impl_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/router_env/; then - echo "router_env_changes_exist=false" >> $GITHUB_ENV - else - echo "router_env_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/test_utils/; then - echo "test_utils_changes_exist=false" >> $GITHUB_ENV - else - echo "test_utils_changes_exist=true" >> $GITHUB_ENV - fi - if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then - echo "pm_auth_changes_exist=false" >> $GITHUB_ENV - else - echo "pm_auth_changes_exist=true" >> $GITHUB_ENV - fi - - - name: Cargo hack api_models - if: env.api_models_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p api_models - - - name: Cargo hack cards - if: env.cards_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p cards - - - name: Cargo hack common_enums - if: env.common_enums_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p common_enums - - - name: Cargo hack common_utils - if: env.common_utils_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p common_utils - - - name: Cargo hack diesel_models - if: env.diesel_models_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p diesel_models - - - name: Cargo hack drainer - if: env.drainer_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p drainer - - - name: Cargo hack external_services - if: env.external_services_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p external_services - - - name: Cargo hack masking - if: env.masking_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p masking - - - name: Cargo hack redis_interface - if: env.redis_interface_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p redis_interface - - - name: Cargo hack pm_auth - if: env.pm_auth_changes_exist == 'true' + # Obtain a list of workspace members + workspace_members="$( + cargo metadata --format-version 1 --no-deps \ + | jq --compact-output --monochrome-output --raw-output '.workspace_members | sort | .[] | split(" ")[0]' + )" + + PACKAGES_CHECKED=() + PACKAGES_SKIPPED=() + + while IFS= read -r package_name; do + # Obtain comma-separated list of transitive workspace dependencies for each workspace member + change_paths="$(cargo tree --all-features --no-dedupe --prefix none --package "${package_name}" \ + | grep 'crates/' \ + | sort --unique \ + | awk --field-separator ' ' '{ printf "crates/%s\n", $1 }' | paste -d ',' -s -)" + + # Store change paths in an array by splitting `change_paths` by comma + IFS=',' read -ra change_paths <<< "${change_paths}" + + # A package must be checked if any of its transitive dependencies (or itself) has been modified + if git diff --exit-code --quiet "origin/${GITHUB_BASE_REF}" -- "${change_paths[@]}"; then + printf '::debug::Skipping `%s` since none of these paths were modified: %s\n' "${package_name}" "${change_paths[*]}" + PACKAGES_SKIPPED+=("${package_name}") + else + printf '::debug::Checking `%s` since at least one of these paths was modified: %s\n' "${package_name}" "${change_paths[*]}" + PACKAGES_CHECKED+=("${package_name}") + fi + done <<< "${workspace_members}" + + printf '::notice::Packages checked: %s; Packages skipped: %s\n' "${PACKAGES_CHECKED[*]}" "${PACKAGES_SKIPPED[*]}" + echo "PACKAGES_CHECKED=${PACKAGES_CHECKED[*]}" >> ${GITHUB_ENV} + echo "PACKAGES_SKIPPED=${PACKAGES_SKIPPED[*]}" >> ${GITHUB_ENV} + + - name: Run `cargo hack` on modified crates shell: bash - run: cargo hack check --each-feature --no-dev-deps -p pm_auth - - - name: Cargo hack router - if: env.router_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --skip kms,basilisk,kv_store,accounts_cache,openapi --no-dev-deps -p router - - - name: Cargo hack router_derive - if: env.router_derive_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p router_derive - - - name: Cargo hack storage_impl - if: env.storage_impl_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p storage_impl - - - name: Cargo hack router_env - if: env.router_env_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p router_env - - - name: Cargo hack test_utils - if: env.test_utils_changes_exist == 'true' - shell: bash - run: cargo hack check --each-feature --no-dev-deps -p test_utils + run: | + # Store packages to check in an array by splitting `PACKAGES_CHECKED` by space + IFS=' ' read -ra PACKAGES_CHECKED <<< "${PACKAGES_CHECKED}" + + for package in "${PACKAGES_CHECKED[@]}"; do + printf '::group::Running `cargo hack` on package `%s`\n' "${package}" + cargo hack check --each-feature --all-targets --package "${package}" + echo '::endgroup::' + done typos: name: Spell check diff --git a/.github/workflows/CI-push.yml b/.github/workflows/CI-push.yml index a6a4bde5a5d..90b301bbd9e 100644 --- a/.github/workflows/CI-push.yml +++ b/.github/workflows/CI-push.yml @@ -80,8 +80,8 @@ jobs: - name: Cargo hack if: ${{ github.event_name == 'push' }} shell: bash - run: cargo hack check --each-feature --no-dev-deps - + run: cargo hack check --workspace --each-feature --all-targets + - name: Cargo build release if: ${{ github.event_name == 'merge_group' }} shell: bash @@ -166,7 +166,7 @@ jobs: - name: Cargo hack if: ${{ github.event_name == 'push' }} shell: bash - run: cargo hack check --each-feature --no-dev-deps + run: cargo hack check --workspace --each-feature --all-targets - name: Cargo build release if: ${{ github.event_name == 'merge_group' }} diff --git a/.github/workflows/connector-ui-sanity-tests.yml b/.github/workflows/connector-ui-sanity-tests.yml index d4317681a11..f3d4635ab11 100644 --- a/.github/workflows/connector-ui-sanity-tests.yml +++ b/.github/workflows/connector-ui-sanity-tests.yml @@ -84,22 +84,31 @@ jobs: 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@v4 - - name: Decrypt connector auth file + - name: Download Encrypted TOML from S3 and Decrypt if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} env: + AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} + AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} + CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} + DESTINATION_FILE_NAME: "connector_auth.toml.gpg" + S3_SOURCE_FILE_NAME: "cf05a6ab-525e-4888-98b3-3b4a443b87c0.toml.gpg" shell: bash - run: ./scripts/decrypt_connector_auth.sh + run: | + mkdir -p ${HOME}/target/secrets ${HOME}/target/test + aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" + gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output "${HOME}/target/test/connector_auth.toml" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" - name: Set connector auth file path in env if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} shell: bash - run: echo "CONNECTOR_AUTH_FILE_PATH=$HOME/target/test/connector_auth.toml" >> $GITHUB_ENV + run: echo "CONNECTOR_AUTH_FILE_PATH=${HOME}/target/test/connector_auth.toml" >> $GITHUB_ENV - name: Set connector tests file path in env if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} shell: bash - run: echo "CONNECTOR_TESTS_FILE_PATH=$HOME/target/test/connector_tests.json" >> $GITHUB_ENV + run: echo "CONNECTOR_TESTS_FILE_PATH=${HOME}/target/test/connector_tests.json" >> $GITHUB_ENV - name: Set ignore_browser_profile usage in env if: ${{ (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} @@ -154,9 +163,9 @@ jobs: failed_connectors=() for i in $(echo "$INPUT" | tr "," "\n"); do - echo $i + echo "${i}" if ! cargo test --package test_utils --test connectors -- "${i}_ui::" --test-threads=1; then - failed_connectors+=("$i") + failed_connectors+=("${i}") fi done diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index d5434520715..8cbbed8187c 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -52,27 +52,37 @@ jobs: - name: Repository checkout uses: actions/checkout@v4 - - name: Decrypt connector auth file + - name: Download Encrypted TOML from S3 and Decrypt 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')}} env: + AWS_ACCESS_KEY_ID: ${{ secrets.CONNECTOR_CREDS_AWS_ACCESS_KEY_ID }} + AWS_REGION: ${{ secrets.CONNECTOR_CREDS_AWS_REGION }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.CONNECTOR_CREDS_AWS_SECRET_ACCESS_KEY }} CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} + CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} + DESTINATION_FILE_NAME: "connector_auth.toml.gpg" + S3_SOURCE_FILE_NAME: "cf05a6ab-525e-4888-98b3-3b4a443b87c0.toml.gpg" shell: bash - run: ./scripts/decrypt_connector_auth.sh + run: | + mkdir -p ${HOME}/target/secrets ${HOME}/target/test + + aws s3 cp "${CONNECTOR_CREDS_S3_BUCKET_URI}/${S3_SOURCE_FILE_NAME}" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" + gpg --quiet --batch --yes --decrypt --passphrase="${CONNECTOR_AUTH_PASSPHRASE}" --output "${HOME}/target/test/connector_auth.toml" "${HOME}/target/secrets/${DESTINATION_FILE_NAME}" - name: Set paths in env 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')}} id: config_path shell: bash run: | - echo "CONNECTOR_AUTH_FILE_PATH=$HOME/target/test/connector_auth.toml" >> $GITHUB_ENV + echo "CONNECTOR_AUTH_FILE_PATH=${HOME}/target/test/connector_auth.toml" >> $GITHUB_ENV - name: Fetch keys 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')}} env: TOML_PATH: "./config/development.toml" run: | - LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' $TOML_PATH) - echo "ADMIN_API_KEY=$LOCAL_ADMIN_API_KEY" >> $GITHUB_ENV + LOCAL_ADMIN_API_KEY=$(yq '.secrets.admin_api_key' ${TOML_PATH}) + echo "ADMIN_API_KEY=${LOCAL_ADMIN_API_KEY}" >> $GITHUB_ENV - name: Install Rust 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')}} @@ -118,7 +128,7 @@ jobs: while ! nc -z localhost 8080; do if [ $COUNT -gt 12 ]; then # Wait for up to 2 minutes (12 * 10 seconds) echo "Server did not start within a reasonable time. Exiting." - kill $SERVER_PID + kill ${SERVER_PID} exit 1 else COUNT=$((COUNT+1)) @@ -141,10 +151,10 @@ jobs: export PATH=${NEWMAN_PATH}:${PATH} failed_connectors=() - for i in $(echo "$CONNECTORS" | tr "," "\n"); do - echo $i - if ! cargo run --bin test_utils -- --connector-name="$i" --base-url="$BASE_URL" --admin-api-key="$ADMIN_API_KEY"; then - failed_connectors+=("$i") + for i in $(echo "${CONNECTORS}" | tr "," "\n"); do + echo "${i}" + if ! cargo run --bin test_utils -- --connector-name="${i}" --base-url="${BASE_URL}" --admin-api-key="${ADMIN_API_KEY}"; then + failed_connectors+=("${i}") fi done diff --git a/.github/workflows/release-new-version.yml b/.github/workflows/release-new-version.yml deleted file mode 100644 index 2f8ae7e4819..00000000000 --- a/.github/workflows/release-new-version.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Release a new version - -on: - schedule: - - cron: "30 14 * * 0-4" # Run workflow at 8 PM IST every Sunday-Thursday - - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - # Allow more retries for network requests in cargo (downloading crates) and - # rustup (installing toolchains). This should help to reduce flaky CI failures - # from transient network timeouts or other issues. - CARGO_NET_RETRY: 10 - RUSTUP_MAX_RETRIES: 10 - -jobs: - create-release: - name: Release a new version - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.AUTO_RELEASE_PAT }} - - - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable 2 weeks ago - - - name: Install cocogitto - uses: baptiste0928/cargo-install@v2.2.0 - with: - crate: cocogitto - version: 5.4.0 - - - 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' - - - name: Update Postman collection files from Postman directories - shell: bash - run: | - # maybe we need to move this package.json as we need it in multiple workflows - npm ci - POSTMAN_DIR=postman/collection-dir - POSTMAN_JSON_DIR=postman/collection-json - NEWMAN_PATH=$(pwd)/node_modules/.bin - export PATH=${NEWMAN_PATH}:${PATH} - # generate postman collections for all postman directories - for connector_dir in ${POSTMAN_DIR}/* - do - connector=$(basename ${connector_dir}) - newman dir-import ${POSTMAN_DIR}/${connector} -o ${POSTMAN_JSON_DIR}/${connector}.postman_collection.json - done - - if git add postman && ! git diff --staged --quiet postman; then - git commit --message 'test(postman): update postman collection files' - echo "Changes detected and commited." - fi - - - name: Obtain previous and new tag information - shell: bash - # Only consider tags on current branch when setting PREVIOUS_TAG - run: | - PREVIOUS_TAG="$(git tag --sort='version:refname' --merged | tail --lines 1)" - if [[ "$(cog bump --auto --dry-run)" == *"No conventional commits for your repository that required a bump"* ]]; then - NEW_TAG="$(cog bump --patch --dry-run)" - else - NEW_TAG="$(cog bump --auto --dry-run)" - fi - echo "NEW_TAG=${NEW_TAG}" >> $GITHUB_ENV - echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_ENV - - - name: Update changelog and create tag - shell: bash - if: ${{ env.NEW_TAG != env.PREVIOUS_TAG }} - # Remove prefix 'v' from 'NEW_TAG' as cog bump --version expects only the version number - run: | - cog bump --version ${NEW_TAG#v} - - - name: Push created commit and tag - shell: bash - if: ${{ env.NEW_TAG != env.PREVIOUS_TAG }} - run: | - git push - git push --tags diff --git a/.github/workflows/release-nightly-version-reusable.yml b/.github/workflows/release-nightly-version-reusable.yml new file mode 100644 index 00000000000..accd8c12a91 --- /dev/null +++ b/.github/workflows/release-nightly-version-reusable.yml @@ -0,0 +1,153 @@ +name: Create a nightly tag + +on: + workflow_call: + secrets: + token: + description: GitHub token for authenticating with GitHub + required: true + outputs: + tag: + description: The tag that was created by the workflow + value: ${{ jobs.create-nightly-tag.outputs.tag }} + +env: + # Allow more retries for network requests in cargo (downloading crates) and + # rustup (installing toolchains). This should help to reduce flaky CI failures + # from transient network timeouts or other issues. + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + + # The branch name that this workflow is allowed to run on. + # If the workflow is run on any other branch, this workflow will fail. + ALLOWED_BRANCH_NAME: main + +jobs: + create-nightly-tag: + name: Create a nightly tag + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.token }} + + - name: Check if the workflow is run on an allowed branch + shell: bash + run: | + if [[ "${{ github.ref }}" != "refs/heads/${ALLOWED_BRANCH_NAME}" ]]; then + echo "::error::This workflow is expected to be run from the '${ALLOWED_BRANCH_NAME}' branch. Current branch: '${{ github.ref }}'" + exit 1 + fi + + - name: Check if the latest commit is a tag + shell: bash + run: | + if [[ -n "$(git tag --points-at HEAD)" ]]; then + echo "::error::The latest commit on the branch is already a tag" + exit 1 + fi + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Install git-cliff + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: git-cliff + version: 1.4.0 + + - name: Obtain previous and next tag information + shell: bash + run: | + # Calendar versioning format followed: `YYYY.0M.0D.MICRO` + # - MICRO version number starts from 0 (to allow for multiple tags in a single day) + # - Hotfixes or patches can be suffixed as `-hotfix1` or `-patch1` after the MICRO version number + + CURRENT_UTC_DATE="$(date --utc '+%04Y.%02m.%02d')" + + # Check if any tags exist on the current branch which contain the current UTC date + if ! git tag --merged | grep --quiet "${CURRENT_UTC_DATE}"; then + # Search for date-like tags (no strict checking), sort and obtain previous tag + PREVIOUS_TAG="$( + git tag --merged \ + | grep --extended-regexp '[0-9]{4}\.[0-9]{2}\.[0-9]{2}' \ + | sort --version-sort \ + | tail --lines 1 + )" + + # No tags with current date exist, next tag will be just tagged with current date and micro version number 0 + NEXT_MICRO_VERSION_NUMBER='0' + NEXT_TAG="${CURRENT_UTC_DATE}.${NEXT_MICRO_VERSION_NUMBER}" + + else + # Some tags exist with current date, find out latest micro version number + PREVIOUS_TAG="$( + git tag --merged \ + | grep "${CURRENT_UTC_DATE}" \ + | sort --version-sort \ + | tail --lines 1 + )" + PREVIOUS_MICRO_VERSION_NUMBER="$( + echo -n "${PREVIOUS_TAG}" \ + | sed --regexp-extended 's/[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.([0-9]+))?(-(.+))?/\2/g' + )" + # ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^ ^^^^ + # YEAR MONTH DAY MICRO Any suffix, say `hotfix1` + # + # The 2nd capture group contains the micro version number + + if [[ -z "${PREVIOUS_MICRO_VERSION_NUMBER}" ]]; then + # Micro version number is empty, set next micro version as 1 + NEXT_MICRO_VERSION_NUMBER='1' + else + # Increment previous micro version by 1 and set it as next micro version + NEXT_MICRO_VERSION_NUMBER="$((PREVIOUS_MICRO_VERSION_NUMBER + 1))" + fi + + NEXT_TAG="${CURRENT_UTC_DATE}.${NEXT_MICRO_VERSION_NUMBER}" + fi + + echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_ENV + echo "NEXT_TAG=${NEXT_TAG}" >> $GITHUB_ENV + + - name: Generate changelog + shell: bash + run: | + # Generate changelog content and store it in `release-notes.md` + git-cliff --config '.github/git-cliff-changelog.toml' --strip header --tag "${NEXT_TAG}" "${PREVIOUS_TAG}^.." \ + | sed "/## ${PREVIOUS_TAG}\$/,\$d" \ + | sed '$s/$/\n- - -/' > release-notes.md + + # Append release notes after the specified pattern in `CHANGELOG.md` + sed --in-place '0,/^- - -/!b; /^- - -/{ + a + r release-notes.md + }' CHANGELOG.md + rm release-notes.md + + - 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' + + - name: Commit, tag and push generated changelog + shell: bash + run: | + git add CHANGELOG.md + git commit --message "chore(version): ${NEXT_TAG}" + + git tag "${NEXT_TAG}" HEAD + + git push origin "${ALLOWED_BRANCH_NAME}" + git push origin "${NEXT_TAG}" + + - name: Set job outputs + shell: bash + run: | + echo "tag=${NEXT_TAG}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/release-nightly-version.yml b/.github/workflows/release-nightly-version.yml new file mode 100644 index 00000000000..13e844e7c5d --- /dev/null +++ b/.github/workflows/release-nightly-version.yml @@ -0,0 +1,99 @@ +name: Create a nightly tag + +on: + schedule: + - cron: "0 0 * * 1-5" # Run workflow at 00:00 midnight UTC (05:30 AM IST) every Monday-Friday + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + # Allow more retries for network requests in cargo (downloading crates) and + # rustup (installing toolchains). This should help to reduce flaky CI failures + # from transient network timeouts or other issues. + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + + # The branch name that this workflow is allowed to run on. + # If the workflow is run on any other branch, this workflow will fail. + ALLOWED_BRANCH_NAME: main + +jobs: + update-postman-collections: + name: Update Postman collection JSON files + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.AUTO_RELEASE_PAT }} + + - name: Check if the workflow is run on an allowed branch + shell: bash + run: | + if [[ "${{ github.ref }}" != "refs/heads/${ALLOWED_BRANCH_NAME}" ]]; then + echo "::error::This workflow is expected to be run from the '${ALLOWED_BRANCH_NAME}' branch. Current branch: '${{ github.ref }}'" + exit 1 + fi + + - name: Check if the latest commit is a tag + shell: bash + run: | + if [[ -n "$(git tag --points-at HEAD)" ]]; then + echo "::error::The latest commit on the branch is already a tag" + exit 1 + fi + + - name: Update Postman collection files from Postman directories + shell: bash + run: | + # maybe we need to move this package.json as we need it in multiple workflows + npm ci + + POSTMAN_DIR="postman/collection-dir" + POSTMAN_JSON_DIR="postman/collection-json" + NEWMAN_PATH="$(pwd)/node_modules/.bin" + export PATH="${NEWMAN_PATH}:${PATH}" + + # generate Postman collection JSON files for all Postman collection directories + for connector_dir in "${POSTMAN_DIR}"/* + do + connector="$(basename "${connector_dir}")" + newman dir-import "${POSTMAN_DIR}/${connector}" -o "${POSTMAN_JSON_DIR}/${connector}.postman_collection.json" + done + + if git add postman && ! git diff --staged --quiet postman; then + echo "POSTMAN_COLLECTION_FILES_UPDATED=true" >> $GITHUB_ENV + echo "Postman collection files have been modified" + else + echo "Postman collection files have no modifications" + fi + + - name: Set git configuration + shell: bash + if: ${{ env.POSTMAN_COLLECTION_FILES_UPDATED == 'true' }} + run: | + git config --local user.name 'github-actions' + git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' + + - name: Commit and push updated Postman collections if modified + shell: bash + if: ${{ env.POSTMAN_COLLECTION_FILES_UPDATED == 'true' }} + run: | + git add postman + git commit --message 'chore(postman): update Postman collection files' + + git push origin "${ALLOWED_BRANCH_NAME}" + + create-nightly-tag: + name: Create a nightly tag + uses: ./.github/workflows/release-nightly-version-reusable.yml + needs: + - update-postman-collections + secrets: + token: ${{ secrets.AUTO_RELEASE_PAT }} diff --git a/.github/workflows/release-stable-version.yml b/.github/workflows/release-stable-version.yml new file mode 100644 index 00000000000..93bd71ef779 --- /dev/null +++ b/.github/workflows/release-stable-version.yml @@ -0,0 +1,154 @@ +name: Release a stable version + +on: + workflow_dispatch: + inputs: + bump_type: + description: The part of the semantic version to bump. + required: true + type: choice + options: + - patch + - minor + +jobs: + create-semver-tag: + name: Create a SemVer tag + runs-on: ubuntu-latest + + steps: + - name: Generate GitHub app token + id: generate_app_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@v4 + with: + fetch-depth: 0 + + - name: Check if the input is valid CalVer tag + shell: bash + run: | + if [[ ${{github.ref}} =~ ^refs/tags/[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.([0-9]+))?(-(.+))?$ ]]; then + echo "${{github.ref}} is a valid CalVer tag." + else + echo "::error::${{github.ref}} is not a valid CalVer tag." + exit 1 + fi + + - name: Check if user is authorized to trigger workflow + shell: bash + env: + GH_TOKEN: ${{ steps.generate_app_token.outputs.token }} + run: | + echo "::add-mask::${GH_TOKEN}" + + function is_user_team_member() { + username="${1}" + team_slug="${2}" + org_name=${{ github.repository_owner }} + + # We obtain HTTP status code since the API returns: + # - 200 status code if the user is a member of the specified team + # - 404 status code if the user is not a member of the specified team + # + # We cannot use the GitHub CLI since it does not seem to provide a way to obtain + # only the HTTP status code (yet). + status_code="$( + curl \ + --location \ + --silent \ + --output /dev/null \ + --write-out '%{http_code}' \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + "https://api.github.com/orgs/${org_name}/teams/${team_slug}/memberships/${username}" + )" + + # Returns a boolean value, allowing it to be directly used in if conditions + [[ status_code -eq 200 ]] + } + + allowed_teams=('hyperswitch-admins' 'hyperswitch-maintainers') + is_user_authorized=false + username=${{ github.triggering_actor }} + + for team in "${allowed_teams[@]}"; do + if is_user_team_member "${username}" "${team}"; then + is_user_authorized=true + break + fi + done + + if ${is_user_authorized}; then + echo "${username} is authorized to trigger workflow" + else + printf -v allowed_teams_comma_separated '%s, ' "${allowed_teams[@]}" + echo "::error::${username} is not authorized to trigger workflow; must be a member of one of these teams: ${allowed_teams_comma_separated%, }" + exit 1 + fi + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Install git-cliff + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: git-cliff + version: 1.4.0 + + - name: Install convco + uses: baptiste0928/cargo-install@v2.2.0 + with: + crate: convco + version: 0.5.0 + + - name: Obtain previous and next tag information + shell: bash + run: | + PREVIOUS_TAG="v$(convco version --prefix 'v')" + NEXT_TAG="v$(convco version --prefix 'v' "--${{ inputs.bump_type }}")" + + echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_ENV + echo "NEXT_TAG=${NEXT_TAG}" >> $GITHUB_ENV + + # We make use of GitHub API calls to create the tag to have signed tags + - name: Create SemVer tag + shell: bash + env: + GH_TOKEN: ${{ steps.generate_app_token.outputs.token }} + run: | + # Create a lightweight tag to point to the checked out CalVer tag + gh api \ + --method POST \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + '/repos/{owner}/{repo}/git/refs' \ + --raw-field "ref=refs/tags/${NEXT_TAG}" \ + --raw-field 'sha=${{ github.sha }}' + + - name: Generate changelog + shell: bash + run: | + # Override git-cliff tag pattern to only consider SemVer tags + export GIT_CLIFF__GIT__TAG_PATTERN='v[0-9]*' + + # Update heading format in git-cliff changelog template to include date + sed -i 's/## {{ version }}/## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }})/' .github/git-cliff-changelog.toml + + # Generate changelog content and store it in `release-notes.md` + git-cliff --config '.github/git-cliff-changelog.toml' --strip header --tag "${NEXT_TAG}" "${PREVIOUS_TAG}^.." \ + | sed "/## ${PREVIOUS_TAG}\$/,\$d" > release-notes.md + + - name: Upload changelog as build artifact + uses: actions/upload-artifact@v4 + with: + name: release-notes.md + path: release-notes.md + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 62804a712fa..81ef10ad213 100644 --- a/.gitignore +++ b/.gitignore @@ -261,7 +261,3 @@ result* # node_modules node_modules/ - -**/connector_auth.toml -**/sample_auth.toml -**/auth.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfcc08d08e..b17ee4964b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,482 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.01.18.0 + +### Features + +- **connector_events:** Added api to fetch connector event logs ([#3319](https://github.com/juspay/hyperswitch/pull/3319)) ([`68a3a28`](https://github.com/juspay/hyperswitch/commit/68a3a280676c8309f9becffae545b134b5e1f2ea)) +- **payment_method:** Add capability to store bank details using /payment_methods endpoint ([#3113](https://github.com/juspay/hyperswitch/pull/3113)) ([`01c2de2`](https://github.com/juspay/hyperswitch/commit/01c2de223f60595d77c06a59a40dfe041e02cfee)) + +### Bug Fixes + +- **core:** Add validation for authtype and metadata in update payment connector ([#3305](https://github.com/juspay/hyperswitch/pull/3305)) ([`52f38d3`](https://github.com/juspay/hyperswitch/commit/52f38d3d5a7d035e8211e1f51c8f982232e2d7ab)) +- **events:** Fix event generation for paymentmethods list ([#3337](https://github.com/juspay/hyperswitch/pull/3337)) ([`ac8d81b`](https://github.com/juspay/hyperswitch/commit/ac8d81b32b3d91b875113d32782a8c62e39ba2a8)) + +### Refactors + +- **connector:** [cybersource] recurring mandate flow ([#3354](https://github.com/juspay/hyperswitch/pull/3354)) ([`387c1c4`](https://github.com/juspay/hyperswitch/commit/387c1c491bdc413ae361d04f0be25eaa58e72fa9)) +- [Noon] adding new field max_amount to mandate request ([#3209](https://github.com/juspay/hyperswitch/pull/3209)) ([`eb2a61d`](https://github.com/juspay/hyperswitch/commit/eb2a61d8597995838f21b8233653c691118b2191)) + +### Miscellaneous Tasks + +- **router:** Remove recon from default features ([#3370](https://github.com/juspay/hyperswitch/pull/3370)) ([`928beec`](https://github.com/juspay/hyperswitch/commit/928beecdd7fe9e09b38ffe750627ca4af94ffc93)) + +**Full Changelog:** [`2024.01.17.0...2024.01.18.0`](https://github.com/juspay/hyperswitch/compare/2024.01.17.0...2024.01.18.0) + +- - - + +## 2024.01.17.0 + +### Features + +- **connector:** [BANKOFAMERICA] Implement 3DS flow for cards ([#3343](https://github.com/juspay/hyperswitch/pull/3343)) ([`d533c98`](https://github.com/juspay/hyperswitch/commit/d533c98b5107fb6876c11b183eb9bc382a77a2f1)) +- **recon:** Add recon APIs ([#3345](https://github.com/juspay/hyperswitch/pull/3345)) ([`8678f8d`](https://github.com/juspay/hyperswitch/commit/8678f8d1448b5ce430931bfbbc269ef979d9eea7)) + +### Bug Fixes + +- **connector_onboarding:** Check if connector exists for the merchant account and add reset tracking id API ([#3229](https://github.com/juspay/hyperswitch/pull/3229)) ([`58cc8d6`](https://github.com/juspay/hyperswitch/commit/58cc8d6109ce49d385b06c762ab3f6670f5094eb)) +- **payment_link:** Added expires_on in payment response ([#3332](https://github.com/juspay/hyperswitch/pull/3332)) ([`5ad3f89`](https://github.com/juspay/hyperswitch/commit/5ad3f8939afafce3eec39704dcaa92270b384dcd)) + +**Full Changelog:** [`2024.01.12.1...2024.01.17.0`](https://github.com/juspay/hyperswitch/compare/2024.01.12.1...2024.01.17.0) + +- - - + +## 2024.01.12.1 + +### Miscellaneous Tasks + +- **config:** Add merchant_secret config for webhooks for cashtocode and volt in wasm dashboard ([#3333](https://github.com/juspay/hyperswitch/pull/3333)) ([`57f2cff`](https://github.com/juspay/hyperswitch/commit/57f2cff75e58b0a7811492a1fdb636f59dcefbd0)) +- Add api reference for blocklist ([#3336](https://github.com/juspay/hyperswitch/pull/3336)) ([`f381d86`](https://github.com/juspay/hyperswitch/commit/f381d86b7c9fa79d632991c74cab53d0181231c6)) + +**Full Changelog:** [`2024.01.12.0...2024.01.12.1`](https://github.com/juspay/hyperswitch/compare/2024.01.12.0...2024.01.12.1) + +- - - + +## 2024.01.12.0 + +### Features + +- **connector:** + - [BOA/Cyb] Include merchant metadata in capture and void requests ([#3308](https://github.com/juspay/hyperswitch/pull/3308)) ([`5a5400c`](https://github.com/juspay/hyperswitch/commit/5a5400cf5b539996b2f327c51d4a07b4a86fd1be)) + - [Volt] Add support for refund webhooks ([#3326](https://github.com/juspay/hyperswitch/pull/3326)) ([`e376f68`](https://github.com/juspay/hyperswitch/commit/e376f68c167a289957a4372df108797088ab1f6e)) + - [BOA/CYB] Store AVS response in connector_metadata ([#3271](https://github.com/juspay/hyperswitch/pull/3271)) ([`e75b11e`](https://github.com/juspay/hyperswitch/commit/e75b11e98ac4c8d37c842c8ee0ccf361dcb52793)) +- **euclid_wasm:** Config changes for NMI ([#3329](https://github.com/juspay/hyperswitch/pull/3329)) ([`ed07c5b`](https://github.com/juspay/hyperswitch/commit/ed07c5ba90868a3132ca90d72219db3ba8978232)) +- **outgoingwebhookevent:** Adding api for query to fetch outgoing webhook events log ([#3310](https://github.com/juspay/hyperswitch/pull/3310)) ([`54d44be`](https://github.com/juspay/hyperswitch/commit/54d44bef730c0679f3535f66e89e88139d70ba2e)) +- **payment_link:** Added sdk layout option payment link ([#3207](https://github.com/juspay/hyperswitch/pull/3207)) ([`6117652`](https://github.com/juspay/hyperswitch/commit/61176524ca0c11c605538a1da9a267837193e1ec)) +- **router:** Payment_method block ([#3056](https://github.com/juspay/hyperswitch/pull/3056)) ([`bb09613`](https://github.com/juspay/hyperswitch/commit/bb096138b5937092badd02741fb869ee35e2e3cc)) +- **users:** Invite user without email ([#3328](https://github.com/juspay/hyperswitch/pull/3328)) ([`6a47063`](https://github.com/juspay/hyperswitch/commit/6a4706323c61f3722dc543993c55084dc9ff9850)) +- Feat(connector): [cybersource] Implement 3DS flow for cards ([#3290](https://github.com/juspay/hyperswitch/pull/3290)) ([`6fb3b00`](https://github.com/juspay/hyperswitch/commit/6fb3b00e82d1e3c03dc1c816ffa6353cc7991a53)) +- Add support for card extended bin in payment attempt ([#3312](https://github.com/juspay/hyperswitch/pull/3312)) ([`cc3eefd`](https://github.com/juspay/hyperswitch/commit/cc3eefd317117d761cdcc76804f3510952d4cec2)) + +### Bug Fixes + +- **core:** Surcharge with saved card failure ([#3318](https://github.com/juspay/hyperswitch/pull/3318)) ([`5a1a3da`](https://github.com/juspay/hyperswitch/commit/5a1a3da7502ce9e13546b896477d82719162d5b6)) +- **refund:** Add merchant_connector_id in refund ([#3303](https://github.com/juspay/hyperswitch/pull/3303)) ([`af43b07`](https://github.com/juspay/hyperswitch/commit/af43b07e4394458db478bc16e5fb8d3b0d636a31)) +- **router:** Add config to avoid connector tokenization for `apple pay` `simplified flow` ([#3234](https://github.com/juspay/hyperswitch/pull/3234)) ([`4f9c04b`](https://github.com/juspay/hyperswitch/commit/4f9c04b856761b9c0486abad4c36de191da2c460)) +- Update amount_capturable based on intent_status and payment flow ([#3278](https://github.com/juspay/hyperswitch/pull/3278)) ([`469ea20`](https://github.com/juspay/hyperswitch/commit/469ea20214aa7c1a3b4b86520724c2509ae37b0b)) + +### Refactors + +- **router:** + - Flagged order_details validation to skip validation ([#3116](https://github.com/juspay/hyperswitch/pull/3116)) ([`8626bda`](https://github.com/juspay/hyperswitch/commit/8626bda6d5aa9e7531edc7ea50ed4f30c3b7227a)) + - Restricted list payment method Customer to api-key based ([#3100](https://github.com/juspay/hyperswitch/pull/3100)) ([`9eaebe8`](https://github.com/juspay/hyperswitch/commit/9eaebe8db3d83105ef1e8fc784241e1fb795dd22)) + +### Miscellaneous Tasks + +- Remove connector auth TOML files from `.gitignore` and `.dockerignore` ([#3330](https://github.com/juspay/hyperswitch/pull/3330)) ([`9f6ef3f`](https://github.com/juspay/hyperswitch/commit/9f6ef3f2240052053b5b7df0a13a5503d8141d56)) + +**Full Changelog:** [`2024.01.11.0...2024.01.12.0`](https://github.com/juspay/hyperswitch/compare/2024.01.11.0...2024.01.12.0) + +- - - + +## 2024.01.11.0 + +### Features + +- **core:** Add new payments webhook events ([#3212](https://github.com/juspay/hyperswitch/pull/3212)) ([`e0e28b8`](https://github.com/juspay/hyperswitch/commit/e0e28b87c0647252918ef110cd7614c46b5cf943)) +- **payment_link:** Add status page for payment link ([#3213](https://github.com/juspay/hyperswitch/pull/3213)) ([`50e4d79`](https://github.com/juspay/hyperswitch/commit/50e4d797da31b570b5920b33d77c24a21d9871e2)) + +### Bug Fixes + +- **euclid_wasm:** Update braintree config prod ([#3288](https://github.com/juspay/hyperswitch/pull/3288)) ([`8830563`](https://github.com/juspay/hyperswitch/commit/8830563748ed20c40b7a21a66e9ad9fd02ddcf0e)) + +### Refactors + +- **connector:** [bluesnap] add connector_txn_id fallback for webhook ([#3315](https://github.com/juspay/hyperswitch/pull/3315)) ([`a69e876`](https://github.com/juspay/hyperswitch/commit/a69e876f8212cb94202686e073005c23b1b2fc35)) +- Removed basilisk feature ([#3281](https://github.com/juspay/hyperswitch/pull/3281)) ([`612f8d9`](https://github.com/juspay/hyperswitch/commit/612f8d9d5f5bcba78aa64c3128cc72be0f2860ea)) + +### Miscellaneous Tasks + +- Nits and small code improvements found during investigation of PR#3168 ([#3259](https://github.com/juspay/hyperswitch/pull/3259)) ([`fe3cf54`](https://github.com/juspay/hyperswitch/commit/fe3cf54781302c733c1682ded2c1735544407a5f)) + +**Full Changelog:** [`2024.01.10.0...2024.01.11.0`](https://github.com/juspay/hyperswitch/compare/2024.01.10.0...2024.01.11.0) + +- - - + +## 2024.01.10.0 + +### Features + +- **Connector:** [VOLT] Add support for Payments Webhooks ([#3155](https://github.com/juspay/hyperswitch/pull/3155)) ([`eba7896`](https://github.com/juspay/hyperswitch/commit/eba789640b72cdfbc17d0994d16ce111a1788fe5)) +- **pm_list:** Add required fields for Ideal ([#3183](https://github.com/juspay/hyperswitch/pull/3183)) ([`1c3c5f6`](https://github.com/juspay/hyperswitch/commit/1c3c5f6b0cff9a0037175ba92c002cdf4249108d)) + +### Bug Fixes + +- **connector:** + - [BOA/CYB] Fix Metadata Error ([#3283](https://github.com/juspay/hyperswitch/pull/3283)) ([`71044a1`](https://github.com/juspay/hyperswitch/commit/71044a14ed87ac0cd7d2bb2009f0e59c79bd344c)) + - [BOA, Cybersource] capture error_code ([#3239](https://github.com/juspay/hyperswitch/pull/3239)) ([`ecf51b5`](https://github.com/juspay/hyperswitch/commit/ecf51b5e3a30f055634edfafcd36f64cef535a53)) +- **outgoingwebhookevents:** Throw an error when outgoing webhook events env var not found ([#3291](https://github.com/juspay/hyperswitch/pull/3291)) ([`ee044a0`](https://github.com/juspay/hyperswitch/commit/ee044a0be811a53842c69f64c27d9995d84b7040)) +- **users:** Added merchant name is list merchants ([#3289](https://github.com/juspay/hyperswitch/pull/3289)) ([`8a354f4`](https://github.com/juspay/hyperswitch/commit/8a354f42295a3167d0e846c9522bc091ebdca3f4)) +- **wasm:** Fix failing `wasm-pack build` for `euclid_wasm` ([#3284](https://github.com/juspay/hyperswitch/pull/3284)) ([`5eb6711`](https://github.com/juspay/hyperswitch/commit/5eb67114646674fe227f073e417f26beb97e9a43)) + +### Refactors + +- Pass customer object to `make_pm_data` ([#3246](https://github.com/juspay/hyperswitch/pull/3246)) ([`36c32c3`](https://github.com/juspay/hyperswitch/commit/36c32c377ae788c96b578303eae5d029e3044b7c)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`8fc68ad`](https://github.com/juspay/hyperswitch/commit/8fc68adc7fb6a23d4a2970a05f5739db6010a53d)) + +**Full Changelog:** [`2024.01.08.0...2024.01.10.0`](https://github.com/juspay/hyperswitch/compare/2024.01.08.0...2024.01.10.0) + +- - - + +## 2024.01.08.0 + +### Features + +- **analytics:** Adding outgoing webhooks kafka event ([#3140](https://github.com/juspay/hyperswitch/pull/3140)) ([`1d26df2`](https://github.com/juspay/hyperswitch/commit/1d26df28bc5e1db359272b40adae70bfba9b7360)) +- **connector:** Add Revoke mandate flow ([#3261](https://github.com/juspay/hyperswitch/pull/3261)) ([`90ac26a`](https://github.com/juspay/hyperswitch/commit/90ac26a92f837568be5181108fdb1272171bbf23)) +- **merchant_account:** Add list multiple merchants in `MerchantAccountInterface` ([#3220](https://github.com/juspay/hyperswitch/pull/3220)) ([`c3172ef`](https://github.com/juspay/hyperswitch/commit/c3172ef60603325a1d9e5cab45e72d23a383e218)) +- **payments:** Add payment id in all the payment logs ([#3142](https://github.com/juspay/hyperswitch/pull/3142)) ([`7766245`](https://github.com/juspay/hyperswitch/commit/7766245478f72b0bc942922b1138c87a239be153)) +- **pm_list:** Add required fields for eps ([#3169](https://github.com/juspay/hyperswitch/pull/3169)) ([`bfd8a5a`](https://github.com/juspay/hyperswitch/commit/bfd8a5a31abb3c95cc9ca21689d5c30a6dc4ce8d)) +- Add deep health check ([#3210](https://github.com/juspay/hyperswitch/pull/3210)) ([`f30ba89`](https://github.com/juspay/hyperswitch/commit/f30ba89884d3abf2356cf1870d833a97d2411f69)) +- Include version number in response headers and on application startup ([#3045](https://github.com/juspay/hyperswitch/pull/3045)) ([`252443a`](https://github.com/juspay/hyperswitch/commit/252443a50dc48939eb08b3bcd67273bb71bbe349)) + +### Bug Fixes + +- **analytics:** + - Fixed response code to 501 ([#3119](https://github.com/juspay/hyperswitch/pull/3119)) ([`00008c1`](https://github.com/juspay/hyperswitch/commit/00008c16c1c20f1f34381d0fc7e55ef05183e776)) + - Added response to the connector outgoing event ([#3129](https://github.com/juspay/hyperswitch/pull/3129)) ([`d152c3a`](https://github.com/juspay/hyperswitch/commit/d152c3a1ca70c39f5c64edf63b5995f6cf02c88a)) +- **connector:** + - [NMI] Populating `ErrorResponse` with required fields and Mapping `connector_response_reference_id` ([#3214](https://github.com/juspay/hyperswitch/pull/3214)) ([`64babd3`](https://github.com/juspay/hyperswitch/commit/64babd34786ba8e6f63aa1dba1cbd1bc6264f2ac)) + - [Stripe] Deserialization Error while parsing Dispute Webhook Body ([#3256](https://github.com/juspay/hyperswitch/pull/3256)) ([`01b4ac3`](https://github.com/juspay/hyperswitch/commit/01b4ac30e40a55b05fe3585d0544b21125762bc7)) +- **router:** + - Multiple incremental_authorizations with kv enabled ([#3185](https://github.com/juspay/hyperswitch/pull/3185)) ([`f78d02d`](https://github.com/juspay/hyperswitch/commit/f78d02d981dd7b35f2150f204b327847b811badd)) + - Payment link api contract change ([#2975](https://github.com/juspay/hyperswitch/pull/2975)) ([`3cd7496`](https://github.com/juspay/hyperswitch/commit/3cd74966b279dc1c43935dc1bceb1c69b9eb0643)) +- **user:** Add integration_completed enum in metadata type ([#3245](https://github.com/juspay/hyperswitch/pull/3245)) ([`3ab71fb`](https://github.com/juspay/hyperswitch/commit/3ab71fbd5ac86f12cf19d17561e428d33c51a4cf)) +- **users:** Fix wrong redirection url in magic link ([#3217](https://github.com/juspay/hyperswitch/pull/3217)) ([`000e644`](https://github.com/juspay/hyperswitch/commit/000e64438838461ea930545405fb2ee0d3c4356c)) +- Introduce net_amount field in payment response ([#3115](https://github.com/juspay/hyperswitch/pull/3115)) ([`23e0c63`](https://github.com/juspay/hyperswitch/commit/23e0c6354185d666771c07b8534e42380cc50812)) + +### Refactors + +- **api_lock:** Allow api lock on psync only when force sync is true ([#3242](https://github.com/juspay/hyperswitch/pull/3242)) ([`ac5349c`](https://github.com/juspay/hyperswitch/commit/ac5349cd7160f67f7a56f48f54981cf3dc1e5b52)) +- **drainer:** Change logic for trimming the stream and refactor for modularity ([#3128](https://github.com/juspay/hyperswitch/pull/3128)) ([`de7a607`](https://github.com/juspay/hyperswitch/commit/de7a607e66847ff4bbddcbbafa50d54a56f02f62)) +- **euclid_wasm:** Update wasm config ([#3222](https://github.com/juspay/hyperswitch/pull/3222)) ([`7ea50c3`](https://github.com/juspay/hyperswitch/commit/7ea50c3a78bc1a091077c23999a69dda1cf0f463)) +- Address panics due to indexing and slicing ([#3233](https://github.com/juspay/hyperswitch/pull/3233)) ([`34318bc`](https://github.com/juspay/hyperswitch/commit/34318bc1f12a1298e8993021a2d516cf86049980)) + +### Miscellaneous Tasks + +- Address Rust 1.75 clippy lints ([#3231](https://github.com/juspay/hyperswitch/pull/3231)) ([`c8279b1`](https://github.com/juspay/hyperswitch/commit/c8279b110e6c55784f042aebb956931e1870b0ca)) + +**Full Changelog:** [`v1.106.1...2024.01.08.0`](https://github.com/juspay/hyperswitch/compare/v1.106.1...2024.01.08.0) + +- - - + +## 1.106.1 (2024-01-05) + +### Bug Fixes + +- **connector:** [iatapay] change refund amount ([#3244](https://github.com/juspay/hyperswitch/pull/3244)) ([`e79604b`](https://github.com/juspay/hyperswitch/commit/e79604bd4681a69802f3c3169dd94424e3688e42)) + +**Full Changelog:** [`v1.106.0...v1.106.1`](https://github.com/juspay/hyperswitch/compare/v1.106.0...v1.106.1) + +- - - + + +## 1.106.0 (2024-01-04) + +### Features + +- **connector:** + - [BOA] Populate merchant_defined_information with metadata ([#3208](https://github.com/juspay/hyperswitch/pull/3208)) ([`18eca7e`](https://github.com/juspay/hyperswitch/commit/18eca7e9fbe6cdc101bd135c4618882b7a5455bf)) + - [CYBERSOURCE] Refactor cybersource ([#3215](https://github.com/juspay/hyperswitch/pull/3215)) ([`e06ba14`](https://github.com/juspay/hyperswitch/commit/e06ba148b666772fe79d7050d0c505dd2f04f87c)) +- **customers:** Add JWT Authentication for `/customers` APIs ([#3179](https://github.com/juspay/hyperswitch/pull/3179)) ([`aefe618`](https://github.com/juspay/hyperswitch/commit/aefe6184ec3e3156877c72988ca0f92454a47e7d)) + +### Bug Fixes + +- **connector:** [Volt] Error handling for auth response ([#3187](https://github.com/juspay/hyperswitch/pull/3187)) ([`a51c54d`](https://github.com/juspay/hyperswitch/commit/a51c54d39d3687c6a06176895435ac66fa194d7b)) +- **core:** Fix recurring mandates flow for cyber source ([#3224](https://github.com/juspay/hyperswitch/pull/3224)) ([`6a1743e`](https://github.com/juspay/hyperswitch/commit/6a1743ebe993d5abb53f2ce1b8b383aa4a9553fb)) +- **middleware:** Add support for logging request-id sent in request ([#3225](https://github.com/juspay/hyperswitch/pull/3225)) ([`0f72b55`](https://github.com/juspay/hyperswitch/commit/0f72b5527aab221b8e69e737e5d19abdd0696150)) + +### Refactors + +- **connector:** [NMI] Include mandatory fields for card 3DS ([#3203](https://github.com/juspay/hyperswitch/pull/3203)) ([`a46b8a7`](https://github.com/juspay/hyperswitch/commit/a46b8a7b05367fbbdbf4fca89d8a6b29110a4e1c)) + +### Testing + +- **postman:** Update postman collection files ([`0248d35`](https://github.com/juspay/hyperswitch/commit/0248d35dd49d2dc7e5e4da6b60a3ee3577c8eac9)) + +### Miscellaneous Tasks + +- Fix channel handling for consumer workflow loop ([#3223](https://github.com/juspay/hyperswitch/pull/3223)) ([`51e1fac`](https://github.com/juspay/hyperswitch/commit/51e1fac556fdd8775e0bbc858b0b3cc50a7e88ec)) + +**Full Changelog:** [`v1.105.0...v1.106.0`](https://github.com/juspay/hyperswitch/compare/v1.105.0...v1.106.0) + +- - - + + +## 1.105.0 (2023-12-23) + +### Features + +- **connector:** [BOA/CYBERSOURCE] Populate connector_transaction_id ([#3202](https://github.com/juspay/hyperswitch/pull/3202)) ([`110d3d2`](https://github.com/juspay/hyperswitch/commit/110d3d211be2edf47533cc5297ae159cad0e5034)) + +**Full Changelog:** [`v1.104.0...v1.105.0`](https://github.com/juspay/hyperswitch/compare/v1.104.0...v1.105.0) + +- - - + + +## 1.104.0 (2023-12-22) + +### Features + +- **connector:** [BOA] Implement apple pay manual flow ([#3191](https://github.com/juspay/hyperswitch/pull/3191)) ([`25fd3d5`](https://github.com/juspay/hyperswitch/commit/25fd3d502e48f10dd3acbdc88caea4007310d4ee)) +- **router:** Make the billing country for apple pay as optional field ([#3188](https://github.com/juspay/hyperswitch/pull/3188)) ([`15987cc`](https://github.com/juspay/hyperswitch/commit/15987cc81ecba3c1d0de4fa0a12424066a8842eb)) + +### Bug Fixes + +- **connector:** + - [Trustpay] Use `connector_request_reference_id` for merchant reference instead of `payment_id` ([#2885](https://github.com/juspay/hyperswitch/pull/2885)) ([`c51c761`](https://github.com/juspay/hyperswitch/commit/c51c761677e8c5ff80de40f8796f340cf1331f96)) + - [BOA/Cyb] Truncate state length to <20 ([#3198](https://github.com/juspay/hyperswitch/pull/3198)) ([`79a18e2`](https://github.com/juspay/hyperswitch/commit/79a18e2bf7bb1f338cf982fb1a152add2ed4e087)) + - [Iatapay] fix error response handling when payment is failed ([#3197](https://github.com/juspay/hyperswitch/pull/3197)) ([`716a74c`](https://github.com/juspay/hyperswitch/commit/716a74cf8449583541c426a5c427c9e32f5b2528)) + - [BOA] Display 2XX Failure Errors ([#3200](https://github.com/juspay/hyperswitch/pull/3200)) ([`07fd9be`](https://github.com/juspay/hyperswitch/commit/07fd9bedf02a1d70fc248fbbab480a5e24a7f077)) + - [CYBERSOURCE] Display 2XX Failure Errors ([#3201](https://github.com/juspay/hyperswitch/pull/3201)) ([`86c2622`](https://github.com/juspay/hyperswitch/commit/86c26221357e14b585f44c6ebe46962c085f6552)) +- **users:** Wrong `user_role` insertion in `invite_user` for new users ([#3193](https://github.com/juspay/hyperswitch/pull/3193)) ([`b06a8d6`](https://github.com/juspay/hyperswitch/commit/b06a8d6e0d7fc4fb1bec30f702d64f0bd5e1068e)) + +**Full Changelog:** [`v1.103.1...v1.104.0`](https://github.com/juspay/hyperswitch/compare/v1.103.1...v1.104.0) + +- - - + + +## 1.103.1 (2023-12-21) + +### Bug Fixes + +- **connector:** + - Remove set_body method for connectors implementing default get_request_body ([#3182](https://github.com/juspay/hyperswitch/pull/3182)) ([`a5e141b`](https://github.com/juspay/hyperswitch/commit/a5e141b542622e7065f0e0070a3cddacde78fd8a)) + - [Paypal] remove shipping address as mandatory field for paypal wallet ([#3181](https://github.com/juspay/hyperswitch/pull/3181)) ([`680ed60`](https://github.com/juspay/hyperswitch/commit/680ed603c5113ec29fbd13c4c633e18ad4ad10ee)) + +**Full Changelog:** [`v1.103.0...v1.103.1`](https://github.com/juspay/hyperswitch/compare/v1.103.0...v1.103.1) + +- - - + + +## 1.103.0 (2023-12-20) + +### Features + +- **connector:** + - [NMI] Implement webhook for Payments and Refunds ([#3164](https://github.com/juspay/hyperswitch/pull/3164)) ([`30c1401`](https://github.com/juspay/hyperswitch/commit/30c14019d067ad5f105563f205eb1941010233e8)) + - [BOA] Handle BOA 5XX errors ([#3178](https://github.com/juspay/hyperswitch/pull/3178)) ([`1d80949`](https://github.com/juspay/hyperswitch/commit/1d80949bef1228bf432dc445eaba15afccb030bd)) +- **connector-config:** Add wasm support for dashboard connector configuration ([#3138](https://github.com/juspay/hyperswitch/pull/3138)) ([`b0ffbe9`](https://github.com/juspay/hyperswitch/commit/b0ffbe9355b7e38226994c1ccbbe80cdbc77adde)) +- **db:** Implement `AuthorizationInterface` for `MockDb` ([#3151](https://github.com/juspay/hyperswitch/pull/3151)) ([`396a64f`](https://github.com/juspay/hyperswitch/commit/396a64f3bbad6e75d4b263286a7ef6a2f09b180e)) +- **postman:** [Prophetpay] Add test cases ([#2946](https://github.com/juspay/hyperswitch/pull/2946)) ([`583d7b8`](https://github.com/juspay/hyperswitch/commit/583d7b87a711102e4e62417f3191ac837886eca9)) + +### Bug Fixes + +- **connector:** + - [NMI] Fix response deserialization for vault id creation ([#3166](https://github.com/juspay/hyperswitch/pull/3166)) ([`d44daaf`](https://github.com/juspay/hyperswitch/commit/d44daaf539021a9cbc33c9391172c38825d74dcd)) + - Connector wise validation for zero auth flow ([#3159](https://github.com/juspay/hyperswitch/pull/3159)) ([`45ba128`](https://github.com/juspay/hyperswitch/commit/45ba128b6ab39f513dd114567d9915acf0eaea20)) +- **events:** Add logger for incoming webhook payload ([#3171](https://github.com/juspay/hyperswitch/pull/3171)) ([`cf47a65`](https://github.com/juspay/hyperswitch/commit/cf47a65916fd4fb5c996946ffd579fd6755d02f7)) +- **users:** Send correct `user_role` values in `switch_merchant` response ([#3167](https://github.com/juspay/hyperswitch/pull/3167)) ([`dc589d5`](https://github.com/juspay/hyperswitch/commit/dc589d580f1382874bc755d3719bd3244fdedc67)) + +### Refactors + +- **core:** Fix payment status for 4xx ([#3177](https://github.com/juspay/hyperswitch/pull/3177)) ([`e7949c2`](https://github.com/juspay/hyperswitch/commit/e7949c23b9be56a4cd763d4990c1a95c0fefae95)) +- **payment_methods:** Make the card_holder_name as an empty string if not sent ([#3173](https://github.com/juspay/hyperswitch/pull/3173)) ([`b98e53d`](https://github.com/juspay/hyperswitch/commit/b98e53d5cba5a5af04ada9bd83fa7bd2e27462d9)) + +### Testing + +- **postman:** Update postman collection files ([`6890e90`](https://github.com/juspay/hyperswitch/commit/6890e9029d90bfd518ba23979a0bd507853dc983)) + +### Documentation + +- **connector:** Update connector integration documentation ([#3041](https://github.com/juspay/hyperswitch/pull/3041)) ([`ce5514e`](https://github.com/juspay/hyperswitch/commit/ce5514eadfce240bc4cefb472405f37432a8507b)) + +**Full Changelog:** [`v1.102.1...v1.103.0`](https://github.com/juspay/hyperswitch/compare/v1.102.1...v1.103.0) + +- - - + + +## 1.102.1 (2023-12-18) + +### Bug Fixes + +- **connector:** [BOA/CYBERSOURCE] Update error handling ([#3156](https://github.com/juspay/hyperswitch/pull/3156)) ([`8e484dd`](https://github.com/juspay/hyperswitch/commit/8e484ddab8d3f4463299c7f7e8ce75b8dd628599)) +- **euclid_wasm:** Add function to retrieve keys for 3ds and surcharge decision manager ([#3160](https://github.com/juspay/hyperswitch/pull/3160)) ([`30fe9d1`](https://github.com/juspay/hyperswitch/commit/30fe9d19e4955035a370f8f9ce37963cdb76c68a)) +- **payment_link:** Added amount conversion to base unit based on currency ([#3162](https://github.com/juspay/hyperswitch/pull/3162)) ([`0fa61a9`](https://github.com/juspay/hyperswitch/commit/0fa61a9dd194c5b3688f8f68b056c263d92327d0)) +- Change prodintent name in dashboard metadata ([#3161](https://github.com/juspay/hyperswitch/pull/3161)) ([`8db3361`](https://github.com/juspay/hyperswitch/commit/8db3361d80f674a28a3916830a4b0c1c2b89776a)) + +### Refactors + +- **connector:** + - [Helcim] change error message from not supported to not implemented ([#2850](https://github.com/juspay/hyperswitch/pull/2850)) ([`41b5a82`](https://github.com/juspay/hyperswitch/commit/41b5a82bafa9b0392bb43ed268fefc5187b48636)) + - [Forte] change error message from not supported to not implemented ([#2847](https://github.com/juspay/hyperswitch/pull/2847)) ([`3fc0e2d`](https://github.com/juspay/hyperswitch/commit/3fc0e2d8195948d50f735df5192ae0f8431b432b)) + - [Cryptopay] change error message from not supported to not implemented ([#2846](https://github.com/juspay/hyperswitch/pull/2846)) ([`2d895be`](https://github.com/juspay/hyperswitch/commit/2d895be9856d17cd923665568aa9b6e54fc1a305)) +- **router:** [ACI] change payment error message from not supported to not implemented error ([#2837](https://github.com/juspay/hyperswitch/pull/2837)) ([`cc12e8a`](https://github.com/juspay/hyperswitch/commit/cc12e8a2435e5e47eeec77c620c747b156a3e16b)) +- **users:** Rename `user_roles` and `dashboard_metadata` columns ([#3135](https://github.com/juspay/hyperswitch/pull/3135)) ([`e3589e6`](https://github.com/juspay/hyperswitch/commit/e3589e641c8a0b3b690b82f09a61d512db2d9932)) + +**Full Changelog:** [`v1.102.0+hotfix.1...v1.102.1`](https://github.com/juspay/hyperswitch/compare/v1.102.0+hotfix.1...v1.102.1) + +- - - + + +## 1.102.0 (2023-12-17) + +### Features + +- **connector:** + - [CYBERSOURCE] Implement Google Pay ([#3139](https://github.com/juspay/hyperswitch/pull/3139)) ([`4ae6af4`](https://github.com/juspay/hyperswitch/commit/4ae6af4632bbef5d21c3cb28538dcc4a94a10789)) + - [PlaceToPay] Implement Cards for PlaceToPay ([#3117](https://github.com/juspay/hyperswitch/pull/3117)) ([`107c66f`](https://github.com/juspay/hyperswitch/commit/107c66fec331376aa8c9f1e710e1503793fde119)) + - [CYBERSOURCE] Implement Apple Pay ([#3149](https://github.com/juspay/hyperswitch/pull/3149)) ([`5f53d84`](https://github.com/juspay/hyperswitch/commit/5f53d84a8b92f8aab67d09666b45362b287809ff)) + - [NMI] Implement 3DS for Cards ([#3143](https://github.com/juspay/hyperswitch/pull/3143)) ([`7df4523`](https://github.com/juspay/hyperswitch/commit/7df45235b1b55c3e4f1205169fb512d2aadc98ac)) + +### Bug Fixes + +- **connector:** + - [Checkout] Fix status mapping for checkout ([#3073](https://github.com/juspay/hyperswitch/pull/3073)) ([`5b2c329`](https://github.com/juspay/hyperswitch/commit/5b2c3291d4fbe3c4154c187b4e915dc3365e761a)) + - [Cybersource] signature authentication in incremental_authorization flow ([#3141](https://github.com/juspay/hyperswitch/pull/3141)) ([`d47a7cc`](https://github.com/juspay/hyperswitch/commit/d47a7cc418b0f4bb609d99f4a463a14c39df46e4)) +- [CYBERSOURCE] Fix Status Mapping ([#3144](https://github.com/juspay/hyperswitch/pull/3144)) ([`62c0c47`](https://github.com/juspay/hyperswitch/commit/62c0c47e99f154399687a32caf9999b365da60ae)) + +### Testing + +- **postman:** Update postman collection files ([`d40de4c`](https://github.com/juspay/hyperswitch/commit/d40de4c8b51010a9e6a3164196702a20c2ab3563)) + +### Miscellaneous Tasks + +- **deps:** Bump zerocopy from 0.7.26 to 0.7.31 ([#3136](https://github.com/juspay/hyperswitch/pull/3136)) ([`d8de3c2`](https://github.com/juspay/hyperswitch/commit/d8de3c285c90103da93f0f3fd0241924dabd256f)) +- **events:** Remove duplicate logs ([#3148](https://github.com/juspay/hyperswitch/pull/3148)) ([`a78fed7`](https://github.com/juspay/hyperswitch/commit/a78fed73babace05b4f668ef219909277045ba85)) + +**Full Changelog:** [`v1.101.0...v1.102.0`](https://github.com/juspay/hyperswitch/compare/v1.101.0...v1.102.0) + +- - - + + +## 1.101.0 (2023-12-14) + +### Features + +- **payments:** Add outgoing payments webhooks ([#3133](https://github.com/juspay/hyperswitch/pull/3133)) ([`f457846`](https://github.com/juspay/hyperswitch/commit/f4578463d5e1a0f442aacebdfa7af0460489ba8c)) + +### Bug Fixes + +- **connector:** [CashToCode]Fix cashtocode redirection for evoucher pm type ([#3131](https://github.com/juspay/hyperswitch/pull/3131)) ([`71a86a8`](https://github.com/juspay/hyperswitch/commit/71a86a804e15e4d053f92cfddb36a15cf7b77f7a)) +- **locker:** Fix double serialization for json request ([#3134](https://github.com/juspay/hyperswitch/pull/3134)) ([`70b86b7`](https://github.com/juspay/hyperswitch/commit/70b86b71e4809d2a47c6bc1214f72c37d3325c37)) +- **router:** Add routing cache invalidation on payment connector update ([#3132](https://github.com/juspay/hyperswitch/pull/3132)) ([`1f84865`](https://github.com/juspay/hyperswitch/commit/1f848659f135542fdfa967b3b48ad6cdf69fda2c)) + +**Full Changelog:** [`v1.100.0...v1.101.0`](https://github.com/juspay/hyperswitch/compare/v1.100.0...v1.101.0) + +- - - + + +## 1.100.0 (2023-12-14) + +### Features + +- **connector:** + - [RISKIFIED] Add support for riskified frm connector ([#2533](https://github.com/juspay/hyperswitch/pull/2533)) ([`151a30f`](https://github.com/juspay/hyperswitch/commit/151a30f4eed10924cd93bf7f4f66976af0ab8314)) + - [HELCIM] Add connector_request_reference_id in invoice_number ([#3087](https://github.com/juspay/hyperswitch/pull/3087)) ([`3cc9642`](https://github.com/juspay/hyperswitch/commit/3cc9642f3ac4c07fb675e9ff4032832819d877a1)) +- **core:** Enable surcharge support for all connectors ([#3109](https://github.com/juspay/hyperswitch/pull/3109)) ([`57e1ae9`](https://github.com/juspay/hyperswitch/commit/57e1ae9dea6ff70fb1bca47c479c35026c167bad)) +- **events:** Add type info to outgoing requests & maintain structural & PII type info ([#2956](https://github.com/juspay/hyperswitch/pull/2956)) ([`6e82b0b`](https://github.com/juspay/hyperswitch/commit/6e82b0bd746b405281f79b86a3cd92b550a33f68)) +- **external_services:** Adds encrypt function for KMS ([#3111](https://github.com/juspay/hyperswitch/pull/3111)) ([`bca7cdb`](https://github.com/juspay/hyperswitch/commit/bca7cdb4c14b5fbb40d8cbf59fd1756ad27ac674)) + +### Bug Fixes + +- **api_locking:** Fix the unit interpretation for `LockSettings` expiry ([#3121](https://github.com/juspay/hyperswitch/pull/3121)) ([`3f4167d`](https://github.com/juspay/hyperswitch/commit/3f4167dbd477c793e1a4cc572da0c12d66f2b649)) +- **connector:** [trustpay] make paymentId optional field ([#3101](https://github.com/juspay/hyperswitch/pull/3101)) ([`62a7c30`](https://github.com/juspay/hyperswitch/commit/62a7c3053c5e276091f5bd54a5679caef58a4ace)) +- **docker-compose:** Remove label list from docker compose yml ([#3118](https://github.com/juspay/hyperswitch/pull/3118)) ([`e1e23fd`](https://github.com/juspay/hyperswitch/commit/e1e23fd987cae96e56311d1cfdcb225d9327860c)) +- Validate refund amount with amount_captured instead of amount ([#3120](https://github.com/juspay/hyperswitch/pull/3120)) ([`be13d15`](https://github.com/juspay/hyperswitch/commit/be13d15d3c0214c863e131cf1dbe184d5baec5d7)) + +### Refactors + +- **connector:** [Wise] Error Message For Connector Implementation ([#2952](https://github.com/juspay/hyperswitch/pull/2952)) ([`1add2c0`](https://github.com/juspay/hyperswitch/commit/1add2c059f4fb5653f33e2f3ce454793caf2d595)) +- **payments:** Add support for receiving card_holder_name field as an empty string ([#3127](https://github.com/juspay/hyperswitch/pull/3127)) ([`4d19d8b`](https://github.com/juspay/hyperswitch/commit/4d19d8b1d18f49f02e951c5025d35cf5d62cec1b)) + +### Testing + +- **postman:** Update postman collection files ([`a5618cd`](https://github.com/juspay/hyperswitch/commit/a5618cd5d6eb5b007f7927f05e777e875195a678)) + +**Full Changelog:** [`v1.99.0...v1.100.0`](https://github.com/juspay/hyperswitch/compare/v1.99.0...v1.100.0) + +- - - + + +## 1.99.0 (2023-12-12) + +### Features + +- **connector:** [Placetopay] Add Connector Template Code ([#3084](https://github.com/juspay/hyperswitch/pull/3084)) ([`a7b688a`](https://github.com/juspay/hyperswitch/commit/a7b688aac72e15f782046b9d108aca12f43a9994)) +- Add utility to convert TOML configuration file to list of environment variables ([#3096](https://github.com/juspay/hyperswitch/pull/3096)) ([`2c4599a`](https://github.com/juspay/hyperswitch/commit/2c4599a1cd7e244b6fb11948c88c55c5b8faad76)) + +### Bug Fixes + +- **router:** Make `request_incremental_authorization` optional in payment_intent ([#3086](https://github.com/juspay/hyperswitch/pull/3086)) ([`f7da59d`](https://github.com/juspay/hyperswitch/commit/f7da59d06af11707e210b58a875c013d31c3ee17)) + +### Refactors + +- **email:** Create client every time of sending email ([#3105](https://github.com/juspay/hyperswitch/pull/3105)) ([`fc2f163`](https://github.com/juspay/hyperswitch/commit/fc2f16392148cd66b3c3e67e3e0c782910e37e1f)) + +### Testing + +- **postman:** Update postman collection files ([`aa97821`](https://github.com/juspay/hyperswitch/commit/aa9782164fb7846fe533c5057a17756dc82ede54)) + +### Miscellaneous Tasks + +- **deps:** Update fred and moka ([#3088](https://github.com/juspay/hyperswitch/pull/3088)) ([`129b1e5`](https://github.com/juspay/hyperswitch/commit/129b1e55bd1cbad0243030fd25379f1400eb170c)) + +**Full Changelog:** [`v1.98.0...v1.99.0`](https://github.com/juspay/hyperswitch/compare/v1.98.0...v1.99.0) + +- - - + + +## 1.98.0 (2023-12-11) + +### Features + +- **connector:** Accept connector_transaction_id in error_response of connector flows for Trustpay ([#3060](https://github.com/juspay/hyperswitch/pull/3060)) ([`f53b090`](https://github.com/juspay/hyperswitch/commit/f53b090db87e094f9694481f13af62240c4c422a)) +- **pm_auth:** Pm_auth service migration ([#3047](https://github.com/juspay/hyperswitch/pull/3047)) ([`9c1c44a`](https://github.com/juspay/hyperswitch/commit/9c1c44a706750b14857e9180f5161b61ed89a2ad)) +- **user:** Add `verify_email` API ([#3076](https://github.com/juspay/hyperswitch/pull/3076)) ([`585e009`](https://github.com/juspay/hyperswitch/commit/585e00980c43797f326efb809df9ffd497d1dd26)) +- **users:** Add resend verification email API ([#3093](https://github.com/juspay/hyperswitch/pull/3093)) ([`6d5c25e`](https://github.com/juspay/hyperswitch/commit/6d5c25e3369117acaf5865965769649d524226af)) + +### Bug Fixes + +- **analytics:** Adding api_path to api logs event and to auditlogs api response ([#3079](https://github.com/juspay/hyperswitch/pull/3079)) ([`bf67438`](https://github.com/juspay/hyperswitch/commit/bf674380d5c7e856d0bae75554326aa9017c0201)) +- **config:** Add missing config fields in `docker_compose.toml` ([#3080](https://github.com/juspay/hyperswitch/pull/3080)) ([`1f8116d`](https://github.com/juspay/hyperswitch/commit/1f8116db368aec344d08603045c4cb46c2c25b41)) +- **connector:** [CYBERSOURCE] Remove Phone Number Field From Address ([#3095](https://github.com/juspay/hyperswitch/pull/3095)) ([`72955ec`](https://github.com/juspay/hyperswitch/commit/72955ecc68280773b9c77b4db3d46de95a62f9ed)) +- **drainer:** Properly log deserialization errors ([#3075](https://github.com/juspay/hyperswitch/pull/3075)) ([`42b5bd4`](https://github.com/juspay/hyperswitch/commit/42b5bd4f3d142c9fa12475f36a8b144753ac06e2)) +- **router:** Allow zero amount for payment intent in list payment methods ([#3090](https://github.com/juspay/hyperswitch/pull/3090)) ([`b283b6b`](https://github.com/juspay/hyperswitch/commit/b283b6b662c9f2eabe90473434369d8f7c2369a6)) +- **user:** Add checks for change password ([#3078](https://github.com/juspay/hyperswitch/pull/3078)) ([`26a2611`](https://github.com/juspay/hyperswitch/commit/26a261131b4dbb8570e139127a2c0d356e2820be)) + +### Refactors + +- **payment_methods:** Make the card_holder_name optional for card details in the payment APIs ([#3074](https://github.com/juspay/hyperswitch/pull/3074)) ([`b279591`](https://github.com/juspay/hyperswitch/commit/b279591057cdba6004c99efc82bb856f0bacd1e0)) +- **user:** Add account verification check in signin ([#3082](https://github.com/juspay/hyperswitch/pull/3082)) ([`f7d6e3c`](https://github.com/juspay/hyperswitch/commit/f7d6e3c0149869175a59996e67d3e2d3b6f3b8c2)) + +### Documentation + +- **openapi:** Fix `payment_methods_enabled` OpenAPI spec in merchant connector account APIs ([#3068](https://github.com/juspay/hyperswitch/pull/3068)) ([`b6838c4`](https://github.com/juspay/hyperswitch/commit/b6838c4d1a3a456e28a5f438fcd74a60bedb2539)) + +### Miscellaneous Tasks + +- **configs:** [CYBERSOURCE] Add mandate configs ([#3085](https://github.com/juspay/hyperswitch/pull/3085)) ([`777cd5c`](https://github.com/juspay/hyperswitch/commit/777cd5cdc2342fb7195a06505647fa331725e1dd)) + +**Full Changelog:** [`v1.97.0...v1.98.0`](https://github.com/juspay/hyperswitch/compare/v1.97.0...v1.98.0) + +- - - + + ## 1.97.0 (2023-12-06) ### Features diff --git a/Cargo.lock b/Cargo.lock index 307a5ca2398..7ce0851ba15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,8 +44,8 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "ahash 0.8.3", - "base64 0.21.4", + "ahash 0.8.6", + "base64 0.21.5", "bitflags 1.3.2", "brotli", "bytes 1.5.0", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -118,7 +118,7 @@ dependencies = [ "parse-size", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -220,7 +220,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash 0.7.6", + "ahash 0.7.7", "bytes 1.5.0", "bytestring", "cfg-if 1.0.0", @@ -255,7 +255,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -281,25 +281,26 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.11", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if 1.0.0", - "getrandom 0.2.10", + "getrandom 0.2.11", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -425,12 +426,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "arcstr" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e" - [[package]] name = "argon2" version = "0.5.2" @@ -541,26 +536,6 @@ dependencies = [ "tokio 1.32.0", ] -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix 0.37.25", - "slab", - "socket2 0.4.9", - "waker-fn", -] - [[package]] name = "async-lock" version = "2.8.0" @@ -589,18 +564,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -636,8 +611,8 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "ahash 0.7.6", - "base64 0.21.4", + "ahash 0.7.7", + "base64 0.21.5", "bytes 1.5.0", "cfg-if 1.0.0", "cookie", @@ -1163,9 +1138,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64-simd" @@ -1317,7 +1292,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "syn_derive", ] @@ -1450,6 +1425,7 @@ dependencies = [ "error-stack", "luhn", "masking", + "router_env", "serde", "serde_json", "thiserror", @@ -1609,9 +1585,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.4" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" dependencies = [ "clap_builder", "clap_derive", @@ -1620,9 +1596,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ "anstyle", "bitflags 1.3.2", @@ -1638,7 +1614,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -1693,7 +1669,7 @@ dependencies = [ "once_cell", "phonenumber", "proptest", - "quick-xml", + "quick-xml 0.28.2", "rand 0.8.5", "regex", "reqwest", @@ -1740,6 +1716,29 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "config_importer" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "indexmap 2.1.0", + "serde", + "serde_json", + "toml 0.7.4", +] + +[[package]] +name = "connector_configs" +version = "0.1.0" +dependencies = [ + "api_models", + "serde", + "serde_with", + "toml 0.7.4", + "utoipa", +] + [[package]] name = "constant_time_eq" version = "0.2.6" @@ -2012,7 +2011,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2023,7 +2022,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2033,7 +2032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.14.1", + "hashbrown 0.14.3", "lock_api 0.4.10", "once_cell", "parking_lot_core 0.9.8", @@ -2160,7 +2159,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2190,7 +2189,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2247,7 +2246,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2267,6 +2266,7 @@ name = "drainer" version = "0.1.0" dependencies = [ "async-bb8-diesel", + "async-trait", "bb8", "clap", "common_utils", @@ -2399,9 +2399,10 @@ version = "0.1.0" dependencies = [ "api_models", "common_enums", + "connector_configs", "currency_conversion", "euclid", - "getrandom 0.2.10", + "getrandom 0.2.11", "kgraph_utils", "once_cell", "ron-parser", @@ -2427,7 +2428,7 @@ dependencies = [ "aws-sdk-sesv2", "aws-sdk-sts", "aws-smithy-client", - "base64 0.21.4", + "base64 0.21.5", "common_utils", "dyn-clone", "error-stack", @@ -2545,16 +2546,14 @@ dependencies = [ [[package]] name = "fred" -version = "6.3.2" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15cc18b56395b8b15ffcdcea7fe8586e3a3ccb3d9dc3b9408800d9814efb08e" +checksum = "f2e8094c30c33132e948eb7e1b740cfdaa5a6702610bd3a2744002ec3575cd68" dependencies = [ "arc-swap", - "arcstr", "async-trait", "bytes 1.5.0", "bytes-utils", - "cfg-if 1.0.0", "float-cmp", "futures 0.3.28", "lazy_static", @@ -2563,7 +2562,7 @@ dependencies = [ "rand 0.8.5", "redis-protocol", "semver 1.0.19", - "sha-1 0.10.1", + "socket2 0.5.4", "tokio 1.32.0", "tokio-stream", "tokio-util", @@ -2597,7 +2596,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2609,7 +2608,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2621,7 +2620,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2734,7 +2733,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -2806,9 +2805,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2907,16 +2906,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "allocator-api2", ] @@ -2926,7 +2925,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.3", ] [[package]] @@ -2935,7 +2934,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes 1.5.0", "headers-core", "http", @@ -3140,16 +3139,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -3232,12 +3231,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.3", "serde", ] @@ -3265,17 +3264,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - [[package]] name = "iovec" version = "0.1.4" @@ -3298,7 +3286,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.17", + "rustix", "windows-sys", ] @@ -3342,7 +3330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33a96c4f2128a6f44ecf7c36df2b03dddf5a07b060a4d5ebc0a81e9821f7c60e" dependencies = [ "anyhow", - "base64 0.21.4", + "base64 0.21.5", "flate2", "once_cell", "openssl", @@ -3388,7 +3376,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "pem", "ring", "serde", @@ -3434,9 +3422,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libgit2-sys" @@ -3484,12 +3472,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.8" @@ -3571,6 +3553,7 @@ version = "0.1.0" dependencies = [ "bytes 1.5.0", "diesel", + "erased-serde", "serde", "serde_json", "subtle", @@ -3777,12 +3760,12 @@ dependencies = [ [[package]] name = "moka" -version = "0.11.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6e72583bf6830c956235bff0d5afec8cf2952f579ebad18ae7821a917d950f" +checksum = "d8017ec3548ffe7d4cef7ac0e12b044c01164a74c0f3119420faeaf13490ad8b" dependencies = [ - "async-io", "async-lock", + "async-trait", "crossbeam-channel", "crossbeam-epoch 0.9.15", "crossbeam-utils 0.8.16", @@ -3791,7 +3774,6 @@ dependencies = [ "parking_lot 0.12.1", "quanta", "rustc_version 0.4.0", - "scheduled-thread-pool", "skeptic", "smallvec 1.11.1", "tagptr", @@ -3909,9 +3891,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -4013,7 +3995,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -4299,7 +4281,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -4363,7 +4345,7 @@ dependencies = [ "itertools 0.11.0", "lazy_static", "nom", - "quick-xml", + "quick-xml 0.28.2", "regex", "regex-cache", "serde", @@ -4389,7 +4371,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -4471,22 +4453,6 @@ dependencies = [ "miniz_oxide 0.3.7", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -4509,7 +4475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.19.10", + "toml_edit 0.19.14", ] [[package]] @@ -4547,9 +4513,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -4670,11 +4636,21 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -4755,7 +4731,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.11", ] [[package]] @@ -4861,6 +4837,7 @@ dependencies = [ "serde", "thiserror", "tokio 1.32.0", + "tokio-stream", ] [[package]] @@ -4893,7 +4870,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.11", "redox_syscall 0.2.16", "thiserror", ] @@ -4970,7 +4947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "async-compression", - "base64 0.21.4", + "base64 0.21.5", "bytes 1.5.0", "encoding_rs", "futures-core", @@ -5094,7 +5071,7 @@ dependencies = [ "awc", "aws-config", "aws-sdk-s3", - "base64 0.21.4", + "base64 0.21.5", "bb8", "bigdecimal", "blake3", @@ -5135,6 +5112,7 @@ dependencies = [ "openssl", "pm_auth", "qrcode", + "quick-xml 0.31.0", "rand 0.8.5", "rand_chacha 0.3.1", "rdkafka", @@ -5155,7 +5133,7 @@ dependencies = [ "serde_urlencoded", "serde_with", "serial_test", - "sha-1 0.9.8", + "sha-1", "sqlx", "storage_impl", "strum 0.25.0", @@ -5180,13 +5158,13 @@ name = "router_derive" version = "0.1.0" dependencies = [ "diesel", - "indexmap 2.0.2", + "indexmap 2.1.0", "proc-macro2", "quote", "serde", "serde_json", "strum 0.24.1", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -5246,7 +5224,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.38", + "syn 2.0.48", "walkdir", ] @@ -5335,20 +5313,6 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.37.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys", -] - [[package]] name = "rustix" version = "0.38.17" @@ -5358,7 +5322,7 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.8", + "linux-raw-sys", "windows-sys", ] @@ -5404,7 +5368,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] @@ -5580,9 +5544,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -5600,22 +5564,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -5670,7 +5634,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -5696,15 +5660,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_json", "serde_with_macros", @@ -5713,14 +5677,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -5745,7 +5709,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -5761,17 +5725,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5968,7 +5921,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", "atoi", "base64 0.13.1", "bigdecimal", @@ -6147,7 +6100,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -6169,9 +6122,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -6187,7 +6140,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -6250,7 +6203,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.17", + "rustix", "windows-sys", ] @@ -6295,7 +6248,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -6307,7 +6260,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "test-case-core", ] @@ -6316,7 +6269,7 @@ name = "test_utils" version = "0.1.0" dependencies = [ "async-trait", - "base64 0.21.4", + "base64 0.21.5", "clap", "masking", "rand 0.8.5", @@ -6386,7 +6339,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -6412,9 +6365,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "serde", @@ -6430,9 +6383,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -6576,7 +6529,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -6628,6 +6581,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio 1.32.0", + "tokio-util", ] [[package]] @@ -6745,10 +6699,11 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" dependencies = [ + "indexmap 1.9.3", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.10", + "toml_edit 0.19.14", ] [[package]] @@ -6762,15 +6717,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.4.11", + "winnow", ] [[package]] @@ -6779,9 +6734,9 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "toml_datetime", - "winnow 0.5.19", + "winnow", ] [[package]] @@ -7145,7 +7100,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82b1bc5417102a73e8464c686eef947bdfb99fcdfc0a4f228e81afa9526470a" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_json", "utoipa-gen", @@ -7160,7 +7115,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", ] [[package]] @@ -7186,7 +7141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "atomic", - "getrandom 0.2.10", + "getrandom 0.2.11", "serde", ] @@ -7309,7 +7264,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -7343,7 +7298,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7462,10 +7417,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] @@ -7536,15 +7491,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" -[[package]] -name = "winnow" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "656953b22bcbfb1ec8179d60734981d1904494ecc91f8a3f0ee5c7389bb8eb4b" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.5.19" @@ -7566,13 +7512,13 @@ dependencies = [ [[package]] name = "wiremock" -version = "0.5.19" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f71803d3a1c80377a06221e0530be02035d5b3e854af56c6ece7ac20ac441d" +checksum = "bd7b0b5b253ebc0240d6aac6dd671c495c467420577bf634d3064ae7e6fa2b4c" dependencies = [ "assert-json-diff", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "deadpool", "futures 0.3.28", "futures-timer", @@ -7637,6 +7583,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Makefile b/Makefile index abe0dd50b14..780d5a993c9 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ eq = $(if $(or $(1),$(2)),$(and $(findstring $(1),$(2)),\ $(findstring $(2),$(1))),1) + +ROOT_DIR_WITH_SLASH := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +ROOT_DIR := $(realpath $(ROOT_DIR_WITH_SLASH)) + # # = Targets # @@ -67,6 +71,14 @@ fmt : clippy : cargo clippy --all-features --all-targets -- -D warnings +# Build the DSL crate as a WebAssembly JS library +# +# Usage : +# make euclid-wasm + +euclid-wasm: + wasm-pack build --target web --out-dir $(ROOT_DIR)/wasm --out-name euclid $(ROOT_DIR)/crates/euclid_wasm -- --features dummy_connector + # Run Rust tests of project. # # Usage : @@ -93,4 +105,4 @@ precommit : fmt clippy test hack: - cargo hack check --workspace --each-feature --no-dev-deps \ No newline at end of file + cargo hack check --workspace --each-feature --all-targets diff --git a/README.md b/README.md index db8e820ef14..dfa77ebe066 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ The single API to access payment ecosystems across 130+ countries

Quick Start Guide • + Local Setup Guide • Fast Integration for Stripe Users • Supported Features • FAQs diff --git a/add_connector.md b/add_connector.md index da09ae0024e..7fc3dcb27d1 100644 --- a/add_connector.md +++ b/add_connector.md @@ -9,6 +9,14 @@ This is a guide to contributing new connector to Router. This guide includes ins - Understanding of the Connector APIs which you wish to integrate with Router - Setup of Router repository and running it on local - Access to API credentials for testing the Connector API (you can quickly sign up for sandbox/uat credentials by visiting the website of the connector you wish to integrate) +- Ensure that you have the nightly toolchain installed because the connector template script includes code formatting. + + Install it using `rustup`: + + ```bash + rustup toolchain install nightly + ``` + In Router, there are Connectors and Payment Methods, examples of both are shown below from which the difference is apparent. @@ -17,22 +25,17 @@ In Router, there are Connectors and Payment Methods, examples of both are shown A connector is an integration to fulfill payments. Related use cases could be any of the below - Payment processor (Stripe, Adyen, ChasePaymentTech etc.,) -- Fraud and Risk management platform (like Ravelin, Riskified etc.,) +- Fraud and Risk management platform (like Signifyd, Riskified etc.,) - Payment network (Visa, Master) - Payment authentication services (Cardinal etc.,) - Router supports "Payment Processors" right now. Support will be extended to the other categories in the near future. +Currently, the router is compatible with 'Payment Processors' and 'Fraud and Risk Management' platforms. Support for additional categories will be expanded in the near future. ### What is a Payment Method ? -Each Connector (say, a Payment Processor) could support multiple payment methods +Every Payment Processor has the capability to accommodate various payment methods. Refer to the [Hyperswitch Payment matrix](https://hyperswitch.io/pm-list) to discover the supported processors and payment methods. -- **Cards :** Bancontact , Knet, Mada -- **Bank Transfers :** EPS , giropay, sofort -- **Bank Direct Debit :** Sepa direct debit -- **Wallets :** Apple Pay , Google Pay , Paypal - -Cards and Bank Transfer payment methods are already included in Router. Hence, adding a new connector which offers payment_methods available in Router is easy and requires almost no breaking changes. -Adding a new payment method (say Wallets or Bank Direct Debit) might require some changes in core business logic of Router, which we are actively working upon. +The above mentioned payment methods are already included in Router. Hence, adding a new connector which offers payment_methods available in Router is easy and requires almost no breaking changes. +Adding a new payment method might require some changes in core business logic of Router, which we are actively working upon. ## How to Integrate a Connector @@ -46,8 +49,7 @@ Below is a step-by-step tutorial for integrating a new connector. ### **Generate the template** ```bash -cd scripts -bash add_connector.sh +sh scripts/add_connector.sh ``` For this tutorial `` would be `checkout`. @@ -81,50 +83,59 @@ For example, in case of checkout, the [request](https://api-reference.checkout.c Now let's implement Request type for checkout -```rust -#[derive(Debug, Serialize)] -pub struct CheckoutPaymentsRequest { - pub source: Source, - pub amount: i64, - pub currency: String, - #[serde(default = "generate_processing_channel_id")] - pub processing_channel_id: Cow<'static, str>, -} - -fn generate_processing_channel_id() -> Cow<'static, str> { - "pc_e4mrdrifohhutfurvuawughfwu".into() -} -``` - -Since Router is connector agnostic, only minimal data is sent to connector and optional fields may be ignored. - -Here processing_channel_id, is specific to checkout and implementations of such functions should be inside the checkout directory. -Let's define `Source` - ```rust #[derive(Debug, Serialize)] pub struct CardSource { #[serde(rename = "type")] - pub source_type: Option, - pub number: Option, - pub expiry_month: Option, - pub expiry_year: Option, + pub source_type: CheckoutSourceTypes, + pub number: cards::CardNumber, + pub expiry_month: Secret, + pub expiry_year: Secret, + pub cvv: Secret, } #[derive(Debug, Serialize)] #[serde(untagged)] -pub enum Source { +pub enum PaymentSource { Card(CardSource), - // TODO: Add other sources here. + Wallets(WalletSource), + ApplePayPredecrypt(Box), +} + +#[derive(Debug, Serialize)] +pub struct PaymentsRequest { + pub source: PaymentSource, + pub amount: i64, + pub currency: String, + pub processing_channel_id: Secret, + #[serde(rename = "3ds")] + pub three_ds: CheckoutThreeDS, + #[serde(flatten)] + pub return_url: ReturnUrl, + pub capture: bool, + pub reference: String, } ``` -`Source` is an enum type. Request types will need to derive `Serialize` and response types will need to derive `Deserialize`. For request types `From` needs to be implemented. +Since Router is connector agnostic, only minimal data is sent to connector and optional fields may be ignored. + +Here processing_channel_id, is specific to checkout and implementations of such functions should be inside the checkout directory. +Let's define `PaymentSource` + +`PaymentSource` is an enum type. Request types will need to derive `Serialize` and response types will need to derive `Deserialize`. For request types `From` needs to be implemented. +For request types that involve an amount, the implementation of `TryFrom<&ConnectorRouterData<&T>>` is required: + +```rust +impl TryFrom<&CheckoutRouterData<&T>> for PaymentsRequest +``` +else ```rust -impl<'a> From<&types::RouterData<'a>> for CheckoutRequestType +impl TryFrom for PaymentsRequest ``` +where `T` is a generic type which can be `types::PaymentsAuthorizeRouterData`, `types::PaymentsCaptureRouterData`, etc. + In this impl block we build the request type from RouterData which will almost always contain all the required information you need for payment processing. `RouterData` contains all the information required for processing the payment. @@ -165,39 +176,56 @@ While implementing the Response Type, the important Enum to be defined for every It stores the different status types that the connector can give in its response that is listed in its API spec. Below is the definition for checkout ```rust -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Default, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum CheckoutPaymentStatus { Authorized, + #[default] Pending, #[serde(rename = "Card Verified")] CardVerified, Declined, + Captured, } ``` The important part is mapping it to the Router status codes. ```rust -impl From for enums::AttemptStatus { - fn from(item: CheckoutPaymentStatus) -> Self { - match item { - CheckoutPaymentStatus::Authorized => enums::AttemptStatus::Charged, - CheckoutPaymentStatus::Declined => enums::AttemptStatus::Failure, - CheckoutPaymentStatus::Pending => enums::AttemptStatus::Authorizing, - CheckoutPaymentStatus::CardVerified => enums::AttemptStatus::Pending, +impl ForeignFrom<(CheckoutPaymentStatus, Option)> for enums::AttemptStatus { + fn foreign_from(item: (CheckoutPaymentStatus, Option)) -> Self { + let (status, balances) = item; + + match status { + CheckoutPaymentStatus::Authorized => { + if let Some(Balances { + available_to_capture: 0, + }) = balances + { + Self::Charged + } else { + Self::Authorized + } + } + CheckoutPaymentStatus::Captured => Self::Charged, + CheckoutPaymentStatus::Declined => Self::Failure, + CheckoutPaymentStatus::Pending => Self::AuthenticationPending, + CheckoutPaymentStatus::CardVerified => Self::Pending, } } } ``` +If you're converting ConnectorPaymentStatus to AttemptStatus without any additional conditions, you can employ the `impl From for enums::AttemptStatus`. -Note: `enum::AttemptStatus` is Router status. +Note: A payment intent can have multiple payment attempts. `enums::AttemptStatus` represents the status of a payment attempt. -Router status are given below +Some of the attempt status are given below -- **Charged :** The amount has been debited -- **PendingVBV :** Pending but verified by visa -- **Failure :** The payment Failed -- **Authorizing :** In the process of authorizing. +- **Charged :** The payment attempt has succeeded. +- **Pending :** Payment is in processing state. +- **Failure :** The payment attempt has failed. +- **Authorized :** Payment is authorized. Authorized payment can be voided, captured and partial captured. +- **AuthenticationPending :** Customer action is required. +- **Voided :** The payment was voided and never captured; the funds were returned to the customer. It is highly recommended that the default status is Pending. Only explicit failure and explicit success from the connector shall be marked as success or failure respectively. @@ -213,26 +241,119 @@ impl Default for CheckoutPaymentStatus { Below is rest of the response type implementation for checkout ```rust - -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct CheckoutPaymentsResponse { +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +pub struct PaymentsResponse { id: String, - amount: i64, + amount: Option, + action_id: Option, status: CheckoutPaymentStatus, + #[serde(rename = "_links")] + links: Links, + balances: Option, + reference: Option, + response_code: Option, + response_summary: Option, } -impl<'a> From> for types::RouterData<'a> { - fn from(item: types::ResponseRouterData<'a, CheckoutPaymentsResponse>) -> Self { - types::RouterData { - connector_transaction_id: Some(item.response.id), - amount_received: Some(item.response.amount), - status: enums::Status::from(item.response.status), +#[derive(Deserialize, Debug)] +pub struct ActionResponse { + #[serde(rename = "id")] + pub action_id: String, + pub amount: i64, + #[serde(rename = "type")] + pub action_type: ActionType, + pub approved: Option, + pub reference: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum PaymentsResponseEnum { + ActionResponse(Vec), + PaymentResponse(Box), +} + +impl TryFrom> + for types::PaymentsAuthorizeRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::PaymentsResponseRouterData, + ) -> Result { + let redirection_data = item.response.links.redirect.map(|href| { + services::RedirectForm::from((href.redirection_url, services::Method::Get)) + }); + let status = enums::AttemptStatus::foreign_from(( + item.response.status, + item.data.request.capture_method, + )); + let error_response = if status == enums::AttemptStatus::Failure { + Some(types::ErrorResponse { + status_code: item.http_code, + code: item + .response + .response_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: item + .response + .response_summary + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: item.response.response_summary, + attempt_status: None, + connector_transaction_id: None, + }) + } else { + None + }; + let payments_response_data = types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + item.response.reference.unwrap_or(item.response.id), + ), + }; + Ok(Self { + status, + response: error_response.map_or_else(|| Ok(payments_response_data), Err), ..item.data - } + }) } } ``` +Using an enum for a response struct in Rust is not recommended due to potential deserialization issues where the deserializer attempts to deserialize into all the enum variants. A preferable alternative is to employ a separate enum for the possible response variants and include it as a field within the response struct. + +Some recommended fields that needs to be set on connector request and response + +- **connector_request_reference_id :** Most of the connectors anticipate merchants to include their own reference ID in payment requests. For instance, the merchant's reference ID in the checkout `PaymentRequest` is specified as `reference`. + +```rust + reference: item.router_data.connector_request_reference_id.clone(), +``` +- **connector_response_reference_id :** Merchants might face ambiguity when deciding which ID to use in the connector dashboard for payment identification. It is essential to populate the connector_response_reference_id with the appropriate reference ID, allowing merchants to recognize the transaction. This field can be linked to either `merchant_reference` or `connector_transaction_id`, depending on the field that the connector dashboard search functionality supports. + +```rust + connector_response_reference_id: item.response.reference.or(Some(item.response.id)) +``` + +- **resource_id :** The connector assigns an identifier to a payment attempt, referred to as `connector_transaction_id`. This identifier is represented as an enum variant for the `resource_id`. If the connector does not provide a `connector_transaction_id`, the resource_id is set to `NoResponseId`. + +```rust + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), +``` +- **redirection_data :** For the implementation of a redirection flow (3D Secure, bank redirects, etc.), assign the redirection link to the `redirection_data`. + +```rust + let redirection_data = item.response.links.redirect.map(|href| { + services::RedirectForm::from((href.redirection_url, services::Method::Get)) + }); +``` + + And finally the error type implementation ```rust @@ -251,23 +372,250 @@ Similarly for every API endpoint you can implement request and response types. The `mod.rs` file contains the trait implementations where we use the types in transformers. -There are four types of tasks that are done by implementing traits: - -- **Payment :** For making/initiating payments -- **PaymentSync :** For checking status of the payment -- **Refund :** For initiating refund -- **RefundSync :** For checking status of the Refund. - We create a struct with the connector name and have trait implementations for it. The following trait implementations are mandatory -- **ConnectorCommon :** contains common description of the connector, like the base endpoint, content-type, error message, id. -- **Payment :** Trait Relationship, has impl block. -- **PaymentAuthorize :** Trait Relationship, has impl block. -- **ConnectorIntegration :** For every api endpoint contains the url, using request transform and response transform and headers. -- **Refund :** Trait Relationship, has empty body. -- **RefundExecute :** Trait Relationship, has empty body. -- **RefundSync :** Trait Relationship, has empty body. +**ConnectorCommon :** contains common description of the connector, like the base endpoint, content-type, error response handling, id, currency unit. + +Within the `ConnectorCommon` trait, you'll find the following methods : + + - `id` method corresponds directly to the connector name. + ```rust + fn id(&self) -> &'static str { + "checkout" + } + ``` + - `get_currency_unit` method anticipates you to [specify the accepted currency unit](#set-the-currency-unit) for the connector. + ```rust + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + ``` + - `common_get_content_type` method requires you to provide the accepted content type for the connector API. + ```rust + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + ``` + - `get_auth_header` method accepts common HTTP Authorization headers that are accepted in all `ConnectorIntegration` flows. + ```rust + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth: checkout::CheckoutAuthType = auth_type + .try_into() + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", auth.api_secret.peek()).into_masked(), + )]) + } + ``` + + - `base_url` method is for fetching the base URL of connector's API. Base url needs to be consumed from configs. + ```rust + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.checkout.base_url.as_ref() + } + ``` + - `build_error_response` method is common error response handling for a connector if it is same in all cases + + ```rust + fn build_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: checkout::ErrorResponse = if res.response.is_empty() { + let (error_codes, error_type) = if res.status_code == 401 { + ( + Some(vec!["Invalid api key".to_string()]), + Some("invalid_api_key".to_string()), + ) + } else { + (None, None) + }; + checkout::ErrorResponse { + request_id: None, + error_codes, + error_type, + } + } else { + res.response + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + }; + + router_env::logger::info!(error_response=?response); + let errors_list = response.error_codes.clone().unwrap_or_default(); + let option_error_code_message = conn_utils::get_error_code_error_message_based_on_priority( + self.clone(), + errors_list + .into_iter() + .map(|errors| errors.into()) + .collect(), + ); + Ok(types::ErrorResponse { + status_code: res.status_code, + code: option_error_code_message + .clone() + .map(|error_code_message| error_code_message.error_code) + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: option_error_code_message + .map(|error_code_message| error_code_message.error_message) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: response + .error_codes + .map(|errors| errors.join(" & ")) + .or(response.error_type), + attempt_status: None, + connector_transaction_id: None, + }) + } + ``` + +**ConnectorIntegration :** For every api endpoint contains the url, using request transform and response transform and headers. +Within the `ConnectorIntegration` trait, you'll find the following methods implemented(below mentioned is example for authorized flow): + +- `get_url` method defines endpoint for authorize flow, base url is consumed from `ConnectorCommon` trait. + +```rust + fn get_url( + &self, + _req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "payments")) + } +``` +- `get_headers` method accepts HTTP headers that are accepted for authorize flow. In this context, it is utilized from the `ConnectorCommonExt` trait, as the connector adheres to common headers across various flows. + +```rust + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } +``` + +- `get_request_body` method calls transformers where hyperswitch payment request data is transformed into connector payment request. For constructing the request body have a function `log_and_get_request_body` that allows generic argument which is the struct that is passed as the body for connector integration, and a function that can be use to encode it into String. We log the request in this function, as the struct will be intact and the masked values will be masked. + +```rust + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let connector_router_data = checkout::CheckoutRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = checkout::PaymentsRequest::try_from(&connector_router_data)?; + let checkout_req = types::RequestBody::log_and_get_request_body( + &connector_req, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(checkout_req)) + } +``` + +- `build_request` method assembles the API request by providing the method, URL, headers, and request body as parameters. +```rust + fn build_request( + &self, + req: &types::RouterData< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } +``` +- `handle_response` method calls transformers where connector response data is transformed into hyperswitch response. +```rust + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + res: types::Response, + ) -> CustomResult { + let response: checkout::PaymentsResponse = res + .response + .parse_struct("PaymentIntentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } +``` +- `get_error_response` method to manage error responses. As the handling of checkout errors remains consistent across various flows, we've incorporated it from the `build_error_response` method within the `ConnectorCommon` trait. +```rust + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +``` +**ConnectorCommonExt :** An enhanced trait for `ConnectorCommon` that enables functions with a generic type. This trait includes the `build_headers` method, responsible for constructing both the common headers and the Authorization headers (retrieved from the `get_auth_header` method), returning them as a vector. + +```rust + where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} +``` + +**Payment :** This trait includes several other traits and is meant to represent the functionality related to payments. + +**PaymentAuthorize :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment authorization. + +**PaymentCapture :** This trait extends the `api::ConnectorIntegration `trait with specific types related to manual payment capture. + +**PaymentSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment retrieve. + +**Refund :** This trait includes several other traits and is meant to represent the functionality related to Refunds. + +**RefundExecute :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds create. + +**RefundSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds retrieve. + And the below derive traits @@ -277,13 +625,105 @@ And the below derive traits There is a trait bound to implement refunds, if you don't want to implement refunds you can mark them as `todo!()` but code panics when you initiate refunds then. -Don’t forget to add logs lines in appropriate places. Refer to other connector code for trait implementations. Mostly the rust compiler will guide you to do it easily. Feel free to connect with us in case of any queries and if you want to confirm the status mapping. +### **Set the currency Unit** +The `get_currency_unit` function, part of the ConnectorCommon trait, enables connectors to specify their accepted currency unit as either `Base` or `Minor`. For instance, Paypal designates its currency in the base unit (for example, USD), whereas Hyperswitch processes amounts in the minor unit (for example, cents). If a connector accepts amounts in the base unit, conversion is required, as illustrated. + +``` rust +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for PaypalRouterData +{ + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} +``` + +**Note:** Since the amount is being converted in the aforementioned `try_from`, it is necessary to retrieve amounts from `ConnectorRouterData` in all other `try_from` instances. + +### **Connector utility functions** + +In the `connector/utils.rs` file, you'll discover utility functions that aid in constructing connector requests and responses. We highly recommend using these helper functions for retrieving payment request fields, such as `get_billing_country`, `get_browser_info`, and `get_expiry_date_as_yyyymm`, as well as for validations, including `is_three_ds`, `is_auto_capture`, and more. + +```rust + let json_wallet_data: CheckoutGooglePayData =wallet_data.get_wallet_token_as_json()?; +``` + ### **Test the connector** -Try running the tests in `crates/router/tests/connectors/{{connector-name}}.rs`. +The template code script generates a test file for the connector, containing 20 sanity tests. We anticipate that you will implement these tests when adding a new connector. + +```rust +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[serial_test::serial] +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} +``` + +Utility functions for tests are also available at `tests/connector/utils`. These functions enable you to write tests with ease. + +```rust + /// For initiating payments when `CaptureMethod` is set to `Manual` + /// This doesn't complete the transaction, `PaymentsCapture` needs to be done manually + async fn authorize_payment( + &self, + payment_data: Option, + payment_info: Option, + ) -> Result> { + let integration = self.get_data().connector.get_connector_integration(); + let mut request = self.generate_data( + types::PaymentsAuthorizeData { + confirm: true, + capture_method: Some(diesel_models::enums::CaptureMethod::Manual), + ..(payment_data.unwrap_or(PaymentAuthorizeType::default().0)) + }, + payment_info, + ); + let tx: oneshot::Sender<()> = oneshot::channel().0; + let state = routes::AppState::with_storage( + Settings::new().unwrap(), + StorageImpl::PostgresqlTest, + tx, + Box::new(services::MockApiClient), + ) + .await; + integration.execute_pretasks(&mut request, &state).await?; + Box::pin(call_connector(request, integration)).await + } +``` + +Prior to executing tests in the shell, ensure that the API keys are configured in `crates/router/tests/connectors/sample_auth.toml` and set the environment variable `CONNECTOR_AUTH_FILE_PATH` using the export command. Avoid pushing code with exposed API keys. + +```rust + export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml" + cargo test --package router --test connectors -- checkout --test-threads=1 +``` All tests should pass and add appropriate tests for connector specific payment flows. ### **Build payment request and response from json schema** diff --git a/config/config.example.toml b/config/config.example.toml index eb2574c92ea..d4e11964192 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -52,6 +52,11 @@ default_ttl = 300 # Default TTL for entries, in seconds default_hash_ttl = 900 # Default TTL for hashes entries, in seconds use_legacy_version = false # Resp protocol for fred crate (set this to true if using RESPv2 or redis version < 6) stream_read_count = 1 # Default number of entries to read from stream if not provided in stream read options +auto_pipeline = true # Whether or not the client should automatically pipeline commands across tasks when possible. +disable_auto_backpressure = false # Whether or not to disable the automatic backpressure features when pipelining is enabled. +max_in_flight_commands = 5000 # The maximum number of in-flight commands (per connection) before backpressure will be applied. +default_command_timeout = 0 # An optional timeout to apply to all commands. +max_feed_count = 200 # The maximum number of frames that will be fed to a socket before flushing. # This section provides configs for currency conversion api [forex_api] @@ -134,12 +139,6 @@ connectors_with_delayed_session_response = "trustpay,payme" # List of connectors connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call [jwekey] # 4 priv/pub key pair -locker_key_identifier1 = "" # key identifier for key rotation , should be same as basilisk -locker_key_identifier2 = "" # key identifier for key rotation , should be same as basilisk -locker_encryption_key1 = "" # public key 1 in pem format, corresponding private key in basilisk -locker_encryption_key2 = "" # public key 2 in pem format, corresponding private key in basilisk -locker_decryption_key1 = "" # private key 1 in pem format, corresponding public key in basilisk -locker_decryption_key2 = "" # private key 2 in pem format, corresponding public key in basilisk vault_encryption_key = "" # public key in pem format, corresponding private key in basilisk-hs rust_locker_encryption_key = "" # public key in pem format, corresponding private key in rust locker vault_private_key = "" # private key in pem format, corresponding public key in basilisk-hs @@ -211,9 +210,11 @@ payeezy.base_url = "https://api-cert.payeezy.com/" payme.base_url = "https://sandbox.payme.io/" paypal.base_url = "https://api-m.sandbox.paypal.com/" payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" @@ -338,7 +339,7 @@ sts_role_session_name = "" # An identifier for the assumed role session, used to #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } -checkout = { long_lived_token = false, payment_method = "wallet" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } mollie = { long_lived_token = false, payment_method = "card" } stax = { long_lived_token = true, payment_method = "card,bank_debit" } square = { long_lived_token = false, payment_method = "card" } @@ -350,6 +351,9 @@ stripe = { payment_method = "bank_transfer" } nuvei = { payment_method = "card" } shift4 = { payment_method = "card" } bluesnap = { payment_method = "card" } +bankofamerica = {payment_method = "card"} +cybersource = {payment_method = "card"} +nmi = {payment_method = "card"} [dummy_connector] enabled = true # Whether dummy connector is enabled or not @@ -378,6 +382,8 @@ pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit bank_debit.sepa = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit +bank_redirect.ideal = {connector_list = "stripe,adyen"} # Mandate supported payment method type and connector for bank_redirect + # Required fields info used while listing the payment_method_data [required_fields.pay_later] # payment_method = "pay_later" @@ -439,6 +445,10 @@ ach = { currency = "USD" } [pm_filters.prophetpay] card_redirect = { currency = "USD" } +[pm_filters.helcim] +credit = { currency = "USD" } +debit = { currency = "USD" } + [connector_customer] connector_list = "gocardless,stax,stripe" payout_connector_list = "wise" @@ -459,7 +469,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" #Merchant Cer apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key generate by RSA:2048 algorithm [payment_link] -sdk_url = "http://localhost:9090/dist/HyperLoader.js" +sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js" [payment_method_auth] redis_expiry = 900 @@ -469,6 +479,12 @@ pm_auth_key = "Some_pm_auth_key" [analytics] source = "sqlx" # The Analytics source/strategy to be used +[analytics.clickhouse] +username = "" # Clickhouse username +password = "" # Clickhouse password (optional) +host = "" # Clickhouse host in http(s)://: format +database_name = "" # Clickhouse database name + [analytics.sqlx] username = "db_user" # Analytics DB Username password = "db_pass" # Analytics DB Password @@ -477,6 +493,7 @@ port = 5432 # Analytics DB Port dbname = "hyperswitch_db" # Name of Database pool_size = 5 # Number of connections to keep open connection_timeout = 10 # Timeout for database connection in seconds +queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 client # Config for KV setup [kv_config] @@ -491,3 +508,24 @@ client_id = "paypal_client_id" # Client ID for PayPal onboarding client_secret = "paypal_secret_key" # Secret key for PayPal onboarding partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding enabled = true # Switch to enable or disable PayPal onboarding + +[frm] +enabled = true + +[paypal_onboarding] +client_id = "paypal_client_id" # Client ID for PayPal onboarding +client_secret = "paypal_secret_key" # Secret key for PayPal onboarding +partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding +enabled = true # Switch to enable or disable PayPal onboarding + +[events] +source = "logs" # The event sink to push events supports kafka or logs (stdout) + +[events.kafka] +brokers = [] # Kafka broker urls for bootstrapping the client +intent_analytics_topic = "topic" # Kafka topic to be used for PaymentIntent events +attempt_analytics_topic = "topic" # Kafka topic to be used for PaymentAttempt events +refund_analytics_topic = "topic" # Kafka topic to be used for Refund events +api_logs_topic = "topic" # Kafka topic to be used for incoming api events +connector_logs_topic = "topic" # Kafka topic to be used for connector api events +outgoing_webhook_logs_topic = "topic" # Kafka topic to be used for outgoing webhook events diff --git a/config/development.toml b/config/development.toml index dc542336624..91269005a0f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -31,6 +31,23 @@ dbname = "hyperswitch_db" pool_size = 5 connection_timeout = 10 +[redis] +host = "127.0.0.1" +port = 6379 +pool_size = 5 +reconnect_max_attempts = 5 +reconnect_delay = 5 +default_ttl = 300 +default_hash_ttl = 900 +use_legacy_version = false +stream_read_count = 1 +auto_pipeline = true +disable_auto_backpressure = false +max_in_flight_commands = 5000 +default_command_timeout = 0 +max_feed_count = 200 + + [server] # HTTP Request body limit. Defaults to 32kB request_body_limit = 32768 @@ -63,12 +80,6 @@ fallback_api_key = "YOUR API KEY HERE" redis_lock_timeout = 26000 [jwekey] -locker_key_identifier1 = "" -locker_key_identifier2 = "" -locker_encryption_key1 = "" -locker_encryption_key2 = "" -locker_decryption_key1 = "" -locker_decryption_key2 = "" vault_encryption_key = "" rust_locker_encryption_key = "" vault_private_key = "" @@ -113,6 +124,7 @@ cards = [ "payme", "paypal", "payu", + "placetopay", "powertranz", "prophetpay", "shift4", @@ -185,9 +197,11 @@ payeezy.base_url = "https://api-cert.payeezy.com/" payme.base_url = "https://sandbox.payme.io/" paypal.base_url = "https://api-m.sandbox.paypal.com/" payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" @@ -338,6 +352,10 @@ paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,N credit = { not_available_flows = { capture_method = "manual" } } debit = { not_available_flows = { capture_method = "manual" } } +[pm_filters.helcim] +credit = { currency = "USD" } +debit = { currency = "USD" } + [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,EUR,EUR,CAD,CZK,DKK,EUR,EUR,EUR,EUR,EUR,EUR,EUR,NZD,NOK,PLN,EUR,EUR,SEK,CHF,GBP,USD" } credit = { not_available_flows = { capture_method = "manual" } } @@ -397,7 +415,7 @@ debit = { currency = "USD" } [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } -checkout = { long_lived_token = false, payment_method = "wallet" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } stax = { long_lived_token = true, payment_method = "card,bank_debit" } mollie = {long_lived_token = false, payment_method = "card"} square = {long_lived_token = false, payment_method = "card"} @@ -410,6 +428,9 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +bankofamerica = {payment_method = "card"} +cybersource = {payment_method = "card"} +nmi = {payment_method = "card"} [connector_customer] connector_list = "gocardless,stax,stripe" @@ -443,14 +464,15 @@ connectors_with_webhook_source_verification_call = "paypal" [mandates.supported_payment_methods] pay_later.klarna = { connector_list = "adyen" } -wallet.google_pay = { connector_list = "stripe,adyen" } -wallet.apple_pay = { connector_list = "stripe,adyen" } +wallet.google_pay = { connector_list = "stripe,adyen,cybersource" } +wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon" } wallet.paypal = { connector_list = "adyen" } card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} +bank_redirect.ideal = {connector_list = "stripe,adyen"} [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] @@ -468,7 +490,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" [payment_link] -sdk_url = "http://localhost:9090/dist/HyperLoader.js" +sdk_url = "http://localhost:9050/HyperLoader.js" [payment_method_auth] redis_expiry = 900 @@ -493,7 +515,8 @@ intent_analytics_topic = "hyperswitch-payment-intent-events" attempt_analytics_topic = "hyperswitch-payment-attempt-events" refund_analytics_topic = "hyperswitch-refund-events" api_logs_topic = "hyperswitch-api-log-events" -connector_events_topic = "hyperswitch-connector-api-events" +connector_logs_topic = "hyperswitch-connector-api-events" +outgoing_webhook_logs_topic = "hyperswitch-outgoing-webhook-events" [analytics] source = "sqlx" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 437df22e300..450fe106a31 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -58,12 +58,6 @@ mock_locker = true basilisk_host = "" [jwekey] -locker_key_identifier1 = "" -locker_key_identifier2 = "" -locker_encryption_key1 = "" -locker_encryption_key2 = "" -locker_decryption_key1 = "" -locker_decryption_key2 = "" vault_encryption_key = "" rust_locker_encryption_key = "" vault_private_key = "" @@ -73,6 +67,19 @@ host = "redis-standalone" port = 6379 cluster_enabled = false cluster_urls = ["redis-cluster:6379"] +pool_size = 5 +reconnect_max_attempts = 5 +reconnect_delay = 5 +default_ttl = 300 +default_hash_ttl = 900 +use_legacy_version = false +stream_read_count = 1 +auto_pipeline = true +disable_auto_backpressure = false +max_in_flight_commands = 5000 +default_command_timeout = 0 +max_feed_count = 200 + [refund] max_attempts = 10 @@ -125,9 +132,11 @@ payeezy.base_url = "https://api-cert.payeezy.com/" payme.base_url = "https://sandbox.payme.io/" paypal.base_url = "https://api-m.sandbox.paypal.com/" payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" @@ -188,6 +197,7 @@ cards = [ "payme", "paypal", "payu", + "placetopay", "powertranz", "prophetpay", "shift4", @@ -219,7 +229,7 @@ consumer_group = "SCHEDULER_GROUP" #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } -checkout = { long_lived_token = false, payment_method = "wallet" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } mollie = {long_lived_token = false, payment_method = "card"} stax = { long_lived_token = true, payment_method = "card,bank_debit" } square = {long_lived_token = false, payment_method = "card"} @@ -231,6 +241,9 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +bankofamerica = {payment_method = "card"} +cybersource = {payment_method = "card"} +nmi = {payment_method = "card"} [dummy_connector] enabled = true @@ -298,6 +311,10 @@ cashapp = {country = "US", currency = "USD"} [pm_filters.prophetpay] card_redirect = { currency = "USD" } +[pm_filters.helcim] +credit = { currency = "USD" } +debit = { currency = "USD" } + [pm_filters.stax] credit = { currency = "USD" } debit = { currency = "USD" } @@ -322,6 +339,7 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalp bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} +bank_redirect.ideal = {connector_list = "stripe,adyen"} [connector_customer] connector_list = "gocardless,stax,stripe" @@ -344,7 +362,8 @@ intent_analytics_topic = "hyperswitch-payment-intent-events" attempt_analytics_topic = "hyperswitch-payment-attempt-events" refund_analytics_topic = "hyperswitch-refund-events" api_logs_topic = "hyperswitch-api-log-events" -connector_events_topic = "hyperswitch-connector-api-events" +connector_logs_topic = "hyperswitch-connector-api-events" +outgoing_webhook_logs_topic = "hyperswitch-outgoing-webhook-events" [analytics] source = "sqlx" diff --git a/connector-template/mod.rs b/connector-template/mod.rs index e9945a726a9..6258d437076 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -15,6 +15,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, + RequestContent } }; @@ -158,7 +159,7 @@ impl Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } - fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult, errors::ConnectorError> { + fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult { let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( &self.get_currency_unit(), @@ -187,7 +188,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?) + .set_body(types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?) .build(), )) } @@ -304,7 +305,7 @@ impl &self, _req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -321,7 +322,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body(self, req, connectors)?) + .set_body(types::PaymentsCaptureType::get_request_body(self, req, connectors)?) .build(), )) } @@ -376,7 +377,7 @@ impl Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } - fn get_request_body(&self, req: &types::RefundsRouterData, _connectors: &settings::Connectors,) -> CustomResult, errors::ConnectorError> { + fn get_request_body(&self, req: &types::RefundsRouterData, _connectors: &settings::Connectors,) -> CustomResult { let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( &self.get_currency_unit(), @@ -396,7 +397,7 @@ impl .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers(self, req, connectors)?) - .body(types::RefundExecuteType::get_request_body(self, req, connectors)?) + .set_body(types::RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } @@ -444,7 +445,7 @@ impl .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .body(types::RefundSyncType::get_request_body(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body(self, req, connectors)?) .build(), )) } diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index bdbfb2e4567..60b13693054 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -42,7 +42,6 @@ pub struct {{project-name | downcase | pascal_case}}PaymentsRequest { #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct {{project-name | downcase | pascal_case}}Card { - name: Secret, number: cards::CardNumber, expiry_month: Secret, expiry_year: Secret, @@ -56,7 +55,6 @@ impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&types::Paymen match item.router_data.request.payment_method_data.clone() { api::PaymentMethodData::Card(req_card) => { let card = {{project-name | downcase | pascal_case}}Card { - name: req_card.card_holder_name, number: req_card.card_number, expiry_month: req_card.card_exp_month, expiry_year: req_card.card_exp_year, diff --git a/crates/analytics/Cargo.toml b/crates/analytics/Cargo.toml index f49fe322ae3..25066970ddc 100644 --- a/crates/analytics/Cargo.toml +++ b/crates/analytics/Cargo.toml @@ -28,8 +28,8 @@ error-stack = "0.3.1" futures = "0.3.28" once_cell = "1.18.0" reqwest = { version = "0.11.18", features = ["serde_json"] } -serde = { version = "1.0.163", features = ["derive", "rc"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive", "rc"] } +serde_json = "1.0.108" sqlx = { version = "0.6.3", features = ["postgres", "runtime-actix", "runtime-actix-native-tls", "time", "bigdecimal"] } strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.43" diff --git a/crates/analytics/docs/clickhouse/scripts/api_events_v2.sql b/crates/analytics/docs/clickhouse/scripts/api_events.sql similarity index 83% rename from crates/analytics/docs/clickhouse/scripts/api_events_v2.sql rename to crates/analytics/docs/clickhouse/scripts/api_events.sql index 33f158ce48b..c3fc3d7b06d 100644 --- a/crates/analytics/docs/clickhouse/scripts/api_events_v2.sql +++ b/crates/analytics/docs/clickhouse/scripts/api_events.sql @@ -1,4 +1,4 @@ -CREATE TABLE api_events_v2_queue ( +CREATE TABLE api_events_queue ( `merchant_id` String, `payment_id` Nullable(String), `refund_id` Nullable(String), @@ -14,12 +14,15 @@ CREATE TABLE api_events_v2_queue ( `api_auth_type` LowCardinality(String), `request` String, `response` Nullable(String), + `error` Nullable(String), `authentication_data` Nullable(String), `status_code` UInt32, - `created_at` DateTime CODEC(T64, LZ4), + `created_at_timestamp` DateTime64(3), `latency` UInt128, `user_agent` String, `ip_addr` String, + `hs_latency` Nullable(UInt128), + `http_method` LowCardinality(String), `url_path` String ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', kafka_topic_list = 'hyperswitch-api-log-events', @@ -28,7 +31,7 @@ kafka_format = 'JSONEachRow', kafka_handle_error_mode = 'stream'; -CREATE TABLE api_events_v2_dist ( +CREATE TABLE api_events_dist ( `merchant_id` String, `payment_id` Nullable(String), `refund_id` Nullable(String), @@ -44,13 +47,15 @@ CREATE TABLE api_events_v2_dist ( `api_auth_type` LowCardinality(String), `request` String, `response` Nullable(String), + `error` Nullable(String), `authentication_data` Nullable(String), `status_code` UInt32, - `created_at` DateTime CODEC(T64, LZ4), - `inserted_at` DateTime CODEC(T64, LZ4), + `created_at_timestamp` DateTime64(3), `latency` UInt128, `user_agent` String, `ip_addr` String, + `hs_latency` Nullable(UInt128), + `http_method` LowCardinality(String), `url_path` String, INDEX flowIndex flow_type TYPE bloom_filter GRANULARITY 1, INDEX apiIndex api_flow TYPE bloom_filter GRANULARITY 1, @@ -62,7 +67,7 @@ ORDER BY TTL created_at + toIntervalMonth(6) ; -CREATE MATERIALIZED VIEW api_events_v2_mv TO api_events_v2_dist ( +CREATE MATERIALIZED VIEW api_events_mv TO api_events_dist ( `merchant_id` String, `payment_id` Nullable(String), `refund_id` Nullable(String), @@ -78,13 +83,15 @@ CREATE MATERIALIZED VIEW api_events_v2_mv TO api_events_v2_dist ( `api_auth_type` LowCardinality(String), `request` String, `response` Nullable(String), + `error` Nullable(String), `authentication_data` Nullable(String), `status_code` UInt32, - `created_at` DateTime CODEC(T64, LZ4), - `inserted_at` DateTime CODEC(T64, LZ4), + `created_at_timestamp` DateTime64(3), `latency` UInt128, `user_agent` String, `ip_addr` String, + `hs_latency` Nullable(UInt128), + `http_method` LowCardinality(String), `url_path` String ) AS SELECT @@ -103,16 +110,19 @@ SELECT api_auth_type, request, response, + error, authentication_data, status_code, - created_at, + created_at_timestamp, now() as inserted_at, latency, user_agent, ip_addr, + hs_latency, + http_method, url_path FROM - api_events_v2_queue + api_events_queue where length(_error) = 0; @@ -133,6 +143,6 @@ SELECT _offset AS offset, _raw_message AS raw, _error AS error -FROM api_events_v2_queue +FROM api_events_queue WHERE length(_error) > 0 ; diff --git a/crates/analytics/docs/clickhouse/scripts/connector_events.sql b/crates/analytics/docs/clickhouse/scripts/connector_events.sql new file mode 100644 index 00000000000..5821cd03556 --- /dev/null +++ b/crates/analytics/docs/clickhouse/scripts/connector_events.sql @@ -0,0 +1,97 @@ +CREATE TABLE connector_events_queue ( + `merchant_id` String, + `payment_id` Nullable(String), + `connector_name` LowCardinality(String), + `request_id` String, + `flow` LowCardinality(String), + `request` String, + `response` Nullable(String), + `error` Nullable(String), + `status_code` UInt32, + `created_at` DateTime64(3), + `latency` UInt128, + `method` LowCardinality(String) +) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', +kafka_topic_list = 'hyperswitch-connector-api-events', +kafka_group_name = 'hyper-c1', +kafka_format = 'JSONEachRow', +kafka_handle_error_mode = 'stream'; + + +CREATE TABLE connector_events_dist ( + `merchant_id` String, + `payment_id` Nullable(String), + `connector_name` LowCardinality(String), + `request_id` String, + `flow` LowCardinality(String), + `request` String, + `response` Nullable(String), + `error` Nullable(String), + `status_code` UInt32, + `created_at` DateTime64(3), + `inserted_at` DateTime64(3), + `latency` UInt128, + `method` LowCardinality(String), + INDEX flowIndex flowTYPE bloom_filter GRANULARITY 1, + INDEX connectorIndex connector_name TYPE bloom_filter GRANULARITY 1, + INDEX statusIndex status_code TYPE bloom_filter GRANULARITY 1 +) ENGINE = MergeTree +PARTITION BY toStartOfDay(created_at) +ORDER BY + (created_at, merchant_id, flow_type, status_code, api_flow) +TTL created_at + toIntervalMonth(6) +; + +CREATE MATERIALIZED VIEW connector_events_mv TO connector_events_dist ( + `merchant_id` String, + `payment_id` Nullable(String), + `connector_name` LowCardinality(String), + `request_id` String, + `flow` LowCardinality(String), + `request` String, + `response` Nullable(String), + `error` Nullable(String), + `status_code` UInt32, + `created_at` DateTime64(3), + `latency` UInt128, + `method` LowCardinality(String) +) AS +SELECT + merchant_id, + payment_id, + connector_name, + request_id, + flow, + request, + response, + error, + status_code, + created_at, + now() as inserted_at, + latency, + method, +FROM + connector_events_queue +where length(_error) = 0; + + +CREATE MATERIALIZED VIEW connector_events_parse_errors +( + `topic` String, + `partition` Int64, + `offset` Int64, + `raw` String, + `error` String +) +ENGINE = MergeTree +ORDER BY (topic, partition, offset) +SETTINGS index_granularity = 8192 AS +SELECT + _topic AS topic, + _partition AS partition, + _offset AS offset, + _raw_message AS raw, + _error AS error +FROM connector_events_queue +WHERE length(_error) > 0 +; diff --git a/crates/analytics/docs/clickhouse/scripts/outgoing_webhook_events.sql b/crates/analytics/docs/clickhouse/scripts/outgoing_webhook_events.sql new file mode 100644 index 00000000000..3dc907629d0 --- /dev/null +++ b/crates/analytics/docs/clickhouse/scripts/outgoing_webhook_events.sql @@ -0,0 +1,109 @@ +CREATE TABLE + outgoing_webhook_events_queue ( + `merchant_id` String, + `event_id` Nullable(String), + `event_type` LowCardinality(String), + `outgoing_webhook_event_type` LowCardinality(String), + `payment_id` Nullable(String), + `refund_id` Nullable(String), + `attempt_id` Nullable(String), + `dispute_id` Nullable(String), + `payment_method_id` Nullable(String), + `mandate_id` Nullable(String), + `content` Nullable(String), + `is_error` Bool, + `error` Nullable(String), + `created_at_timestamp` DateTime64(3) + ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', + kafka_topic_list = 'hyperswitch-outgoing-webhook-events', + kafka_group_name = 'hyper-c1', + kafka_format = 'JSONEachRow', + kafka_handle_error_mode = 'stream'; + +CREATE TABLE + outgoing_webhook_events_cluster ( + `merchant_id` String, + `event_id` String, + `event_type` LowCardinality(String), + `outgoing_webhook_event_type` LowCardinality(String), + `payment_id` Nullable(String), + `refund_id` Nullable(String), + `attempt_id` Nullable(String), + `dispute_id` Nullable(String), + `payment_method_id` Nullable(String), + `mandate_id` Nullable(String), + `content` Nullable(String), + `is_error` Bool, + `error` Nullable(String), + `created_at_timestamp` DateTime64(3), + `inserted_at` DateTime DEFAULT now() CODEC(T64, LZ4), + INDEX eventIndex event_type TYPE bloom_filter GRANULARITY 1, + INDEX webhookeventIndex outgoing_webhook_event_type TYPE bloom_filter GRANULARITY 1 + ) ENGINE = MergeTree PARTITION BY toStartOfDay(created_at_timestamp) +ORDER BY ( + created_at_timestamp, + merchant_id, + event_id, + event_type, + outgoing_webhook_event_type + ) TTL inserted_at + toIntervalMonth(6); + +CREATE MATERIALIZED VIEW outgoing_webhook_events_mv TO outgoing_webhook_events_cluster ( + `merchant_id` String, + `event_id` Nullable(String), + `event_type` LowCardinality(String), + `outgoing_webhook_event_type` LowCardinality(String), + `payment_id` Nullable(String), + `refund_id` Nullable(String), + `attempt_id` Nullable(String), + `dispute_id` Nullable(String), + `payment_method_id` Nullable(String), + `mandate_id` Nullable(String), + `content` Nullable(String), + `is_error` Bool, + `error` Nullable(String), + `created_at_timestamp` DateTime64(3), + `inserted_at` DateTime DEFAULT now() CODEC(T64, LZ4), +) AS +SELECT + merchant_id, + event_id, + event_type, + outgoing_webhook_event_type, + payment_id, + refund_id, + attempt_id, + dispute_id, + payment_method_id, + mandate_id, + content, + is_error, + error, + created_at_timestamp, + now() AS inserted_at +FROM + outgoing_webhook_events_queue +where length(_error) = 0; + +CREATE MATERIALIZED VIEW outgoing_webhook_parse_errors ( + `topic` String, + `partition` Int64, + `offset` Int64, + `raw` String, + `error` String +) ENGINE = MergeTree +ORDER BY ( + topic, partition, + offset + ) SETTINGS index_granularity = 8192 AS +SELECT + _topic AS topic, + _partition AS partition, + _offset AS +offset +, + _raw_message AS raw, + _error AS error +FROM + outgoing_webhook_events_queue +WHERE length(_error) > 0; \ No newline at end of file diff --git a/crates/analytics/src/api_event/core.rs b/crates/analytics/src/api_event/core.rs index b368d6374f7..81b82c5dce1 100644 --- a/crates/analytics/src/api_event/core.rs +++ b/crates/analytics/src/api_event/core.rs @@ -8,6 +8,7 @@ use api_models::analytics::{ AnalyticsMetadata, ApiEventFiltersResponse, GetApiEventFiltersRequest, GetApiEventMetricRequest, MetricsResponse, }; +use common_utils::errors::ReportSwitchExt; use error_stack::{IntoReport, ResultExt}; use router_env::{ instrument, logger, @@ -32,16 +33,18 @@ pub async fn api_events_core( merchant_id: String, ) -> AnalyticsResult> { let data = match pool { - AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented) - .into_report() - .attach_printable("SQL Analytics is not implemented for API Events"), + AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented( + "API Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for API Events"), AnalyticsProvider::Clickhouse(pool) => get_api_event(&merchant_id, req, pool).await, AnalyticsProvider::CombinedSqlx(_sqlx_pool, ckh_pool) | AnalyticsProvider::CombinedCkh(_sqlx_pool, ckh_pool) => { get_api_event(&merchant_id, req, ckh_pool).await } } - .change_context(AnalyticsError::UnknownError)?; + .switch()?; Ok(data) } @@ -58,9 +61,11 @@ pub async fn get_filters( let mut res = ApiEventFiltersResponse::default(); for dim in req.group_by_names { let values = match pool { - AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented) - .into_report() - .attach_printable("SQL Analytics is not implemented for API Events"), + AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented( + "API Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for API Events"), AnalyticsProvider::Clickhouse(ckh_pool) | AnalyticsProvider::CombinedSqlx(_, ckh_pool) | AnalyticsProvider::CombinedCkh(_, ckh_pool) => { @@ -68,7 +73,7 @@ pub async fn get_filters( .await } } - .change_context(AnalyticsError::UnknownError)? + .switch()? .into_iter() .filter_map(|fil: ApiEventFilter| match dim { ApiEventDimensions::StatusCode => fil.status_code.map(|i| i.to_string()), diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 964486c9364..f81c29c801c 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -21,6 +21,8 @@ use crate::{ filters::ApiEventFilter, metrics::{latency::LatencyAvg, ApiEventMetricRow}, }, + connector_events::events::ConnectorEventsResult, + outgoing_webhook_event::events::OutgoingWebhookLogsResult, sdk_events::events::SdkEventsResult, types::TableEngine, }; @@ -120,6 +122,8 @@ impl AnalyticsDataSource for ClickhouseClient { } AnalyticsCollection::SdkEvents => TableEngine::BasicTree, AnalyticsCollection::ApiEvents => TableEngine::BasicTree, + AnalyticsCollection::ConnectorEvents => TableEngine::BasicTree, + AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree, } } } @@ -145,6 +149,11 @@ impl super::sdk_events::events::SdkEventsFilterAnalytics for ClickhouseClient {} impl super::api_event::events::ApiLogsFilterAnalytics for ClickhouseClient {} impl super::api_event::filters::ApiEventFilterAnalytics for ClickhouseClient {} impl super::api_event::metrics::ApiEventMetricAnalytics for ClickhouseClient {} +impl super::connector_events::events::ConnectorEventLogAnalytics for ClickhouseClient {} +impl super::outgoing_webhook_event::events::OutgoingWebhookLogsFilterAnalytics + for ClickhouseClient +{ +} #[derive(Debug, serde::Serialize)] struct CkhQuery { @@ -182,6 +191,18 @@ impl TryInto for serde_json::Value { } } +impl TryInto for serde_json::Value { + type Error = Report; + + fn try_into(self) -> Result { + serde_json::from_value(self) + .into_report() + .change_context(ParsingError::StructParseFailure( + "Failed to parse ConnectorEventsResult in clickhouse results", + )) + } +} + impl TryInto for serde_json::Value { type Error = Report; @@ -302,6 +323,18 @@ impl TryInto for serde_json::Value { } } +impl TryInto for serde_json::Value { + type Error = Report; + + fn try_into(self) -> Result { + serde_json::from_value(self) + .into_report() + .change_context(ParsingError::StructParseFailure( + "Failed to parse OutgoingWebhookLogsResult in clickhouse results", + )) + } +} + impl ToSql for PrimitiveDateTime { fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { let format = @@ -326,6 +359,8 @@ impl ToSql for AnalyticsCollection { Self::SdkEvents => Ok("sdk_events_dist".to_string()), Self::ApiEvents => Ok("api_audit_log".to_string()), Self::PaymentIntent => Ok("payment_intents_dist".to_string()), + Self::ConnectorEvents => Ok("connector_events_audit".to_string()), + Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), } } } diff --git a/crates/analytics/src/connector_events.rs b/crates/analytics/src/connector_events.rs new file mode 100644 index 00000000000..c7c31306a2c --- /dev/null +++ b/crates/analytics/src/connector_events.rs @@ -0,0 +1,5 @@ +mod core; +pub mod events; +pub trait ConnectorEventAnalytics: events::ConnectorEventLogAnalytics {} + +pub use self::core::connector_events_core; diff --git a/crates/analytics/src/connector_events/core.rs b/crates/analytics/src/connector_events/core.rs new file mode 100644 index 00000000000..15f841af5f8 --- /dev/null +++ b/crates/analytics/src/connector_events/core.rs @@ -0,0 +1,27 @@ +use api_models::analytics::connector_events::ConnectorEventsRequest; +use common_utils::errors::ReportSwitchExt; +use error_stack::{IntoReport, ResultExt}; + +use super::events::{get_connector_events, ConnectorEventsResult}; +use crate::{errors::AnalyticsResult, types::FiltersError, AnalyticsProvider}; + +pub async fn connector_events_core( + pool: &AnalyticsProvider, + req: ConnectorEventsRequest, + merchant_id: String, +) -> AnalyticsResult> { + let data = match pool { + AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented( + "Connector Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for Connector Events"), + AnalyticsProvider::Clickhouse(ckh_pool) + | AnalyticsProvider::CombinedSqlx(_, ckh_pool) + | AnalyticsProvider::CombinedCkh(_, ckh_pool) => { + get_connector_events(&merchant_id, req, ckh_pool).await + } + } + .switch()?; + Ok(data) +} diff --git a/crates/analytics/src/connector_events/events.rs b/crates/analytics/src/connector_events/events.rs new file mode 100644 index 00000000000..096520777ee --- /dev/null +++ b/crates/analytics/src/connector_events/events.rs @@ -0,0 +1,63 @@ +use api_models::analytics::{ + connector_events::{ConnectorEventsRequest, QueryType}, + Granularity, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow}, +}; +pub trait ConnectorEventLogAnalytics: LoadRow {} + +pub async fn get_connector_events( + merchant_id: &String, + query_param: ConnectorEventsRequest, + pool: &T, +) -> FiltersResult> +where + T: AnalyticsDataSource + ConnectorEventLogAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::ConnectorEvents); + query_builder.add_select_column("*").switch()?; + + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; + match query_param.query_param { + QueryType::Payment { payment_id } => query_builder + .add_filter_clause("payment_id", payment_id) + .switch()?, + } + //TODO!: update the execute_query function to return reports instead of plain errors... + query_builder + .execute_query::(pool) + .await + .change_context(FiltersError::QueryBuildingError)? + .change_context(FiltersError::QueryExecutionFailure) +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ConnectorEventsResult { + pub merchant_id: String, + pub payment_id: String, + pub connector_name: Option, + pub request_id: Option, + pub flow: String, + pub request: String, + pub response: Option, + pub error: Option, + pub status_code: u16, + pub latency: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + pub method: Option, +} diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index 24da77f84f2..501bd58527c 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -7,6 +7,8 @@ mod query; pub mod refunds; pub mod api_event; +pub mod connector_events; +pub mod outgoing_webhook_event; pub mod sdk_events; mod sqlx; mod types; diff --git a/crates/analytics/src/outgoing_webhook_event.rs b/crates/analytics/src/outgoing_webhook_event.rs new file mode 100644 index 00000000000..9919d8bbb0f --- /dev/null +++ b/crates/analytics/src/outgoing_webhook_event.rs @@ -0,0 +1,6 @@ +mod core; +pub mod events; + +pub trait OutgoingWebhookEventAnalytics: events::OutgoingWebhookLogsFilterAnalytics {} + +pub use self::core::outgoing_webhook_events_core; diff --git a/crates/analytics/src/outgoing_webhook_event/core.rs b/crates/analytics/src/outgoing_webhook_event/core.rs new file mode 100644 index 00000000000..5024cc70ec1 --- /dev/null +++ b/crates/analytics/src/outgoing_webhook_event/core.rs @@ -0,0 +1,27 @@ +use api_models::analytics::outgoing_webhook_event::OutgoingWebhookLogsRequest; +use common_utils::errors::ReportSwitchExt; +use error_stack::{IntoReport, ResultExt}; + +use super::events::{get_outgoing_webhook_event, OutgoingWebhookLogsResult}; +use crate::{errors::AnalyticsResult, types::FiltersError, AnalyticsProvider}; + +pub async fn outgoing_webhook_events_core( + pool: &AnalyticsProvider, + req: OutgoingWebhookLogsRequest, + merchant_id: String, +) -> AnalyticsResult> { + let data = match pool { + AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented( + "Outgoing Webhook Events Logs not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for Outgoing Webhook Events"), + AnalyticsProvider::Clickhouse(ckh_pool) + | AnalyticsProvider::CombinedSqlx(_, ckh_pool) + | AnalyticsProvider::CombinedCkh(_, ckh_pool) => { + get_outgoing_webhook_event(&merchant_id, req, ckh_pool).await + } + } + .switch()?; + Ok(data) +} diff --git a/crates/analytics/src/outgoing_webhook_event/events.rs b/crates/analytics/src/outgoing_webhook_event/events.rs new file mode 100644 index 00000000000..e742387e1eb --- /dev/null +++ b/crates/analytics/src/outgoing_webhook_event/events.rs @@ -0,0 +1,90 @@ +use api_models::analytics::{outgoing_webhook_event::OutgoingWebhookLogsRequest, Granularity}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow}, +}; +pub trait OutgoingWebhookLogsFilterAnalytics: LoadRow {} + +pub async fn get_outgoing_webhook_event( + merchant_id: &String, + query_param: OutgoingWebhookLogsRequest, + pool: &T, +) -> FiltersResult> +where + T: AnalyticsDataSource + OutgoingWebhookLogsFilterAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::OutgoingWebhookEvent); + query_builder.add_select_column("*").switch()?; + + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; + query_builder + .add_filter_clause("payment_id", query_param.payment_id) + .switch()?; + + if let Some(event_id) = query_param.event_id { + query_builder + .add_filter_clause("event_id", &event_id) + .switch()?; + } + if let Some(refund_id) = query_param.refund_id { + query_builder + .add_filter_clause("refund_id", &refund_id) + .switch()?; + } + if let Some(dispute_id) = query_param.dispute_id { + query_builder + .add_filter_clause("dispute_id", &dispute_id) + .switch()?; + } + if let Some(mandate_id) = query_param.mandate_id { + query_builder + .add_filter_clause("mandate_id", &mandate_id) + .switch()?; + } + if let Some(payment_method_id) = query_param.payment_method_id { + query_builder + .add_filter_clause("payment_method_id", &payment_method_id) + .switch()?; + } + if let Some(attempt_id) = query_param.attempt_id { + query_builder + .add_filter_clause("attempt_id", &attempt_id) + .switch()?; + } + //TODO!: update the execute_query function to return reports instead of plain errors... + query_builder + .execute_query::(pool) + .await + .change_context(FiltersError::QueryBuildingError)? + .change_context(FiltersError::QueryExecutionFailure) +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct OutgoingWebhookLogsResult { + pub merchant_id: String, + pub event_id: String, + pub event_type: String, + pub outgoing_webhook_event_type: String, + pub payment_id: String, + pub refund_id: Option, + pub attempt_id: Option, + pub dispute_id: Option, + pub payment_method_id: Option, + pub mandate_id: Option, + pub content: Option, + pub is_error: bool, + pub error: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, +} diff --git a/crates/analytics/src/sdk_events/core.rs b/crates/analytics/src/sdk_events/core.rs index 34f23c745b0..46cc636f438 100644 --- a/crates/analytics/src/sdk_events/core.rs +++ b/crates/analytics/src/sdk_events/core.rs @@ -7,6 +7,7 @@ use api_models::analytics::{ AnalyticsMetadata, GetSdkEventFiltersRequest, GetSdkEventMetricRequest, MetricsResponse, SdkEventFiltersResponse, }; +use common_utils::errors::ReportSwitchExt; use error_stack::{IntoReport, ResultExt}; use router_env::{instrument, logger, tracing}; @@ -28,16 +29,18 @@ pub async fn sdk_events_core( publishable_key: String, ) -> AnalyticsResult> { match pool { - AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented) - .into_report() - .attach_printable("SQL Analytics is not implemented for Sdk Events"), + AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented( + "SDK Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for Sdk Events"), AnalyticsProvider::Clickhouse(pool) => get_sdk_event(&publishable_key, req, pool).await, AnalyticsProvider::CombinedSqlx(_sqlx_pool, ckh_pool) | AnalyticsProvider::CombinedCkh(_sqlx_pool, ckh_pool) => { get_sdk_event(&publishable_key, req, ckh_pool).await } } - .change_context(AnalyticsError::UnknownError) + .switch() } #[instrument(skip_all)] @@ -159,9 +162,11 @@ pub async fn get_filters( if let Some(publishable_key) = publishable_key { for dim in req.group_by_names { let values = match pool { - AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented) - .into_report() - .attach_printable("SQL Analytics is not implemented for SDK Events"), + AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented( + "SDK Events not implemented for SQLX", + )) + .into_report() + .attach_printable("SQL Analytics is not implemented for SDK Events"), AnalyticsProvider::Clickhouse(pool) => { get_sdk_event_filter_for_dimension(dim, publishable_key, &req.time_range, pool) .await diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index cdd2647e4e7..7ab8a2aa4bc 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -429,6 +429,10 @@ impl ToSql for AnalyticsCollection { Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::PaymentIntent => Ok("payment_intent".to_string()), + Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ConnectorEvents table is not implemented for Sqlx"))?, + Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, } } } diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index 16d342d3d2e..18e9e9f4334 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -8,6 +8,7 @@ use common_utils::{ use error_stack::{report, Report, ResultExt}; use super::query::QueryBuildingError; +use crate::errors::AnalyticsError; #[derive(serde::Deserialize, Debug, serde::Serialize)] #[serde(rename_all = "snake_case")] @@ -25,6 +26,8 @@ pub enum AnalyticsCollection { SdkEvents, ApiEvents, PaymentIntent, + ConnectorEvents, + OutgoingWebhookEvent, } #[allow(dead_code)] @@ -124,8 +127,8 @@ pub enum FiltersError { #[error("Error running Query")] QueryExecutionFailure, #[allow(dead_code)] - #[error("Not Implemented")] - NotImplemented, + #[error("Not Implemented: {0}")] + NotImplemented(&'static str), } impl ErrorSwitch for QueryBuildingError { @@ -134,4 +137,13 @@ impl ErrorSwitch for QueryBuildingError { } } +impl ErrorSwitch for FiltersError { + fn switch(&self) -> AnalyticsError { + match self { + Self::QueryBuildingError | Self::QueryExecutionFailure => AnalyticsError::UnknownError, + Self::NotImplemented(a) => AnalyticsError::NotImplemented(a), + } + } +} + impl_misc_api_event_type!(AnalyticsDomain); diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index afba129b601..d1f603f188e 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -18,14 +18,15 @@ dummy_connector = ["euclid/dummy_connector", "common_enums/dummy_connector"] detailed_errors = [] payouts = [] frm = [] +recon = [] [dependencies] actix-web = { version = "4.3.1", optional = true } error-stack = "0.3.1" mime = "0.3.17" reqwest = { version = "0.11.18", optional = true } -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" strum = { version = "0.25", features = ["derive"] } time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } url = { version = "2.4.0", features = ["serde"] } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index d35b12152e9..134beacd226 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -95,15 +95,8 @@ pub struct MerchantAccountCreate { #[schema(value_type = Option,example = json!({"type": "single", "data": "signifyd"}))] pub frm_routing_algorithm: Option, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds - ///(900) for 15 mins - #[schema(example = 900)] - pub intent_fulfillment_time: Option, - /// The id of the organization to which the merchant belongs to pub organization_id: Option, - - pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] @@ -185,16 +178,10 @@ pub struct MerchantAccountUpdate { #[schema(value_type = Option,example = json!({"type": "single", "data": "signifyd"}))] pub frm_routing_algorithm: Option, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds - ///(900) for 15 mins - pub intent_fulfillment_time: Option, - /// The default business profile that must be used for creating merchant accounts and payments /// To unset this field, pass an empty string #[schema(max_length = 64)] pub default_profile: Option, - - pub payment_link_config: Option, } #[derive(Clone, Debug, ToSchema, Serialize)] @@ -288,8 +275,6 @@ pub struct MerchantAccountResponse { /// A enum value to indicate the status of recon service. By default it is not_requested. #[schema(value_type = ReconStatus, example = "not_requested")] pub recon_status: enums::ReconStatus, - - pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] @@ -359,7 +344,7 @@ pub mod payout_routing_algorithm { where A: de::MapAccess<'de>, { - let mut output = serde_json::Value::Object(Map::new()); + let mut output = Map::new(); let mut routing_data: String = "".to_string(); let mut routing_type: String = "".to_string(); @@ -367,14 +352,20 @@ pub mod payout_routing_algorithm { match key { "type" => { routing_type = map.next_value()?; - output["type"] = serde_json::Value::String(routing_type.to_owned()); + output.insert( + "type".to_string(), + serde_json::Value::String(routing_type.to_owned()), + ); } "data" => { routing_data = map.next_value()?; - output["data"] = serde_json::Value::String(routing_data.to_owned()); + output.insert( + "data".to_string(), + serde_json::Value::String(routing_data.to_owned()), + ); } f => { - output[f] = map.next_value()?; + output.insert(f.to_string(), map.next_value()?); } } } @@ -392,7 +383,7 @@ pub mod payout_routing_algorithm { } u => Err(de::Error::custom(format!("Unknown routing algorithm {u}"))), }?; - Ok(output) + Ok(serde_json::Value::Object(output)) } } @@ -454,26 +445,6 @@ pub struct PrimaryBusinessDetails { pub business: String, } -#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct PaymentLinkConfig { - #[schema( - max_length = 255, - max_length = 255, - example = "https://i.imgur.com/RfxPFQo.png" - )] - pub merchant_logo: Option, - pub color_scheme: Option, -} - -#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)] -#[serde(deny_unknown_fields)] - -pub struct PaymentLinkColorSchema { - pub background_primary_color: Option, - pub sdk_theme: Option, -} - #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[serde(deny_unknown_fields)] pub struct WebhookDetails { @@ -1042,6 +1013,13 @@ pub struct BusinessProfileCreate { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, } #[derive(Clone, Debug, ToSchema, Serialize)] @@ -1106,6 +1084,13 @@ pub struct BusinessProfileResponse { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] @@ -1163,4 +1148,46 @@ pub struct BusinessProfileUpdate { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct BusinessPaymentLinkConfig { + pub domain_name: Option, + #[serde(flatten)] + pub config: PaymentLinkConfigRequest, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentLinkConfigRequest { + /// custom theme for the payment link + #[schema(value_type = Option, max_length = 255, example = "#4E6ADD")] + pub theme: Option, + /// merchant display logo + #[schema(value_type = Option, max_length = 255, example = "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg")] + pub logo: Option, + /// Custom merchant name for payment link + #[schema(value_type = Option, max_length = 255, example = "hyperswitch")] + pub seller_name: Option, + /// Custom layout for sdk + #[schema(value_type = Option, max_length = 255, example = "accordion")] + pub sdk_layout: Option, +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] +pub struct PaymentLinkConfig { + /// custom theme for the payment link + pub theme: String, + /// merchant display logo + pub logo: String, + /// Custom merchant name for payment link + pub seller_name: String, + /// Custom layout for sdk + pub sdk_layout: String, } diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index 0263427b0fd..c6ca215f9f7 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -12,6 +12,8 @@ use self::{ pub use crate::payments::TimeRange; pub mod api_event; +pub mod connector_events; +pub mod outgoing_webhook_event; pub mod payments; pub mod refunds; pub mod sdk_events; diff --git a/crates/api_models/src/analytics/connector_events.rs b/crates/api_models/src/analytics/connector_events.rs new file mode 100644 index 00000000000..b2974b0a339 --- /dev/null +++ b/crates/api_models/src/analytics/connector_events.rs @@ -0,0 +1,11 @@ +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(tag = "type")] +pub enum QueryType { + Payment { payment_id: String }, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ConnectorEventsRequest { + #[serde(flatten)] + pub query_param: QueryType, +} diff --git a/crates/api_models/src/analytics/outgoing_webhook_event.rs b/crates/api_models/src/analytics/outgoing_webhook_event.rs new file mode 100644 index 00000000000..b6f0aca056f --- /dev/null +++ b/crates/api_models/src/analytics/outgoing_webhook_event.rs @@ -0,0 +1,10 @@ +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct OutgoingWebhookLogsRequest { + pub payment_id: String, + pub event_id: Option, + pub refund_id: Option, + pub dispute_id: Option, + pub mandate_id: Option, + pub payment_method_id: Option, + pub attempt_id: Option, +} diff --git a/crates/api_models/src/blocklist.rs b/crates/api_models/src/blocklist.rs new file mode 100644 index 00000000000..888b9106ccc --- /dev/null +++ b/crates/api_models/src/blocklist.rs @@ -0,0 +1,44 @@ +use common_enums::enums; +use common_utils::events::ApiEventMetric; +use utoipa::ToSchema; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case", tag = "type", content = "data")] +pub enum BlocklistRequest { + CardBin(String), + Fingerprint(String), + ExtendedCardBin(String), +} + +pub type AddToBlocklistRequest = BlocklistRequest; +pub type DeleteFromBlocklistRequest = BlocklistRequest; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct BlocklistResponse { + pub fingerprint_id: String, + #[schema(value_type = BlocklistDataKind)] + pub data_kind: enums::BlocklistDataKind, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: time::PrimitiveDateTime, +} + +pub type AddToBlocklistResponse = BlocklistResponse; +pub type DeleteFromBlocklistResponse = BlocklistResponse; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct ListBlocklistQuery { + #[schema(value_type = BlocklistDataKind)] + pub data_kind: enums::BlocklistDataKind, + #[serde(default = "default_list_limit")] + pub limit: u16, + #[serde(default)] + pub offset: u16, +} + +fn default_list_limit() -> u16 { + 10 +} + +impl ApiEventMetric for BlocklistRequest {} +impl ApiEventMetric for BlocklistResponse {} +impl ApiEventMetric for ListBlocklistQuery {} diff --git a/crates/api_models/src/connector_onboarding.rs b/crates/api_models/src/connector_onboarding.rs index 759d3cb97f1..7e8288d9747 100644 --- a/crates/api_models/src/connector_onboarding.rs +++ b/crates/api_models/src/connector_onboarding.rs @@ -52,3 +52,9 @@ pub struct PayPalOnboardingDone { pub struct PayPalIntegrationDone { pub connector_id: String, } + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct ResetTrackingIdRequest { + pub connector_id: String, + pub connector: enums::Connector, +} diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 21586054055..558223a68ee 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -109,6 +109,7 @@ pub enum Connector { Payme, Paypal, Payu, + Placetopay, Powertranz, Prophetpay, Rapyd, @@ -126,6 +127,7 @@ pub enum Connector { Zen, Signifyd, Plaid, + Riskified, } impl Connector { @@ -199,6 +201,7 @@ impl From for RoutableConnectors { pub enum FrmConnectors { /// Signifyd Risk Manager. Official docs: https://docs.signifyd.com/ Signifyd, + Riskified, } #[cfg(feature = "frm")] @@ -206,6 +209,7 @@ impl From for RoutableConnectors { fn from(value: FrmConnectors) -> Self { match value { FrmConnectors::Signifyd => Self::Signifyd, + FrmConnectors::Riskified => Self::Riskified, } } } diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 457d3fde05b..a8185d2d241 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -5,6 +5,8 @@ mod locker_migration; pub mod payment; #[cfg(feature = "payouts")] pub mod payouts; +#[cfg(feature = "recon")] +pub mod recon; pub mod refund; pub mod routing; pub mod user; @@ -15,9 +17,13 @@ use common_utils::{ impl_misc_api_event_type, }; +#[allow(unused_imports)] use crate::{ admin::*, - analytics::{api_event::*, sdk_events::*, *}, + analytics::{ + api_event::*, connector_events::ConnectorEventsRequest, + outgoing_webhook_event::OutgoingWebhookLogsRequest, sdk_events::*, *, + }, api_keys::*, cards_info::*, disputes::*, @@ -33,7 +39,6 @@ impl ApiEventMetric for TimeRange {} impl_misc_api_event_type!( PaymentMethodId, PaymentsSessionResponse, - PaymentMethodListResponse, PaymentMethodCreate, PaymentLinkInitiateRequest, RetrievePaymentLinkResponse, @@ -89,7 +94,9 @@ impl_misc_api_event_type!( ApiLogsRequest, GetApiEventMetricRequest, SdkEventsRequest, - ReportRequest + ReportRequest, + ConnectorEventsRequest, + OutgoingWebhookLogsRequest ); #[cfg(feature = "stripe")] diff --git a/crates/api_models/src/events/connector_onboarding.rs b/crates/api_models/src/events/connector_onboarding.rs index 998dc384d62..0da89f61da7 100644 --- a/crates/api_models/src/events/connector_onboarding.rs +++ b/crates/api_models/src/events/connector_onboarding.rs @@ -2,11 +2,13 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::connector_onboarding::{ ActionUrlRequest, ActionUrlResponse, OnboardingStatus, OnboardingSyncRequest, + ResetTrackingIdRequest, }; common_utils::impl_misc_api_event_type!( ActionUrlRequest, ActionUrlResponse, OnboardingSyncRequest, - OnboardingStatus + OnboardingStatus, + ResetTrackingIdRequest ); diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index f718dc1ca4d..32d3dc30bd8 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -3,7 +3,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::{ payment_methods::{ CustomerPaymentMethodsListResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, - PaymentMethodResponse, PaymentMethodUpdate, + PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, @@ -119,6 +119,8 @@ impl ApiEventMetric for PaymentMethodListRequest { } } +impl ApiEventMetric for PaymentMethodListResponse {} + impl ApiEventMetric for PaymentListFilterConstraints { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) diff --git a/crates/api_models/src/events/recon.rs b/crates/api_models/src/events/recon.rs new file mode 100644 index 00000000000..aed648f4c86 --- /dev/null +++ b/crates/api_models/src/events/recon.rs @@ -0,0 +1,21 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::recon::{ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest}; + +impl ApiEventMetric for ReconUpdateMerchantRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} + +impl ApiEventMetric for ReconTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} + +impl ApiEventMetric for ReconStatusResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Recon) + } +} diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 92b67572396..c0743c8b8fc 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -1,15 +1,19 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; +#[cfg(feature = "recon")] +use masking::PeekInterface; #[cfg(feature = "dummy_connector")] use crate::user::sample_data::SampleDataRequest; +#[cfg(feature = "recon")] +use crate::user::VerifyTokenResponse; use crate::user::{ dashboard_metadata::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest, - InviteUserResponse, ResetPasswordRequest, SignUpRequest, SignUpWithMerchantIdRequest, - SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest, + InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignUpRequest, + SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest, }; impl ApiEventMetric for DashboardEntryResponse { @@ -21,6 +25,16 @@ impl ApiEventMetric for DashboardEntryResponse { } } +#[cfg(feature = "recon")] +impl ApiEventMetric for VerifyTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::User { + merchant_id: self.merchant_id.clone(), + user_id: self.user_email.peek().to_string(), + }) + } +} + common_utils::impl_misc_api_event_type!( SignUpRequest, SignUpWithMerchantIdRequest, @@ -39,7 +53,8 @@ common_utils::impl_misc_api_event_type!( ResetPasswordRequest, InviteUserRequest, InviteUserResponse, - VerifyEmailRequest + VerifyEmailRequest, + SendVerifyEmailRequest ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/health_check.rs b/crates/api_models/src/health_check.rs new file mode 100644 index 00000000000..d7bb120d017 --- /dev/null +++ b/crates/api_models/src/health_check.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct RouterHealthCheckResponse { + pub database: String, + pub redis: String, + pub locker: String, +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 935944cf74c..1ea79ff6fe8 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -3,6 +3,7 @@ pub mod admin; pub mod analytics; pub mod api_keys; pub mod bank_accounts; +pub mod blocklist; pub mod cards_info; pub mod conditional_configs; pub mod connector_onboarding; @@ -16,6 +17,7 @@ pub mod errors; pub mod events; pub mod files; pub mod gsm; +pub mod health_check; pub mod locker_migration; pub mod mandates; pub mod organization; @@ -24,6 +26,8 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod pm_auth; +#[cfg(feature = "recon")] +pub mod recon; pub mod refunds; pub mod routing; pub mod surcharge_decision_configs; diff --git a/crates/api_models/src/mandates.rs b/crates/api_models/src/mandates.rs index 035f7adec9f..5c0810dc21b 100644 --- a/crates/api_models/src/mandates.rs +++ b/crates/api_models/src/mandates.rs @@ -17,6 +17,12 @@ pub struct MandateRevokedResponse { /// The status for mandates #[schema(value_type = MandateStatus)] pub status: api_enums::MandateStatus, + /// If there was an error while calling the connectors the code is received here + #[schema(example = "E0001")] + pub error_code: Option, + /// If there was an error while calling the connector the error message is received here + #[schema(example = "Failed while verifying the card")] + pub error_message: Option, } #[derive(Default, Debug, Deserialize, Serialize, ToSchema, Clone)] diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 85b0adefca5..3467777da74 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -13,9 +13,7 @@ use utoipa::{schema, ToSchema}; #[cfg(feature = "payouts")] use crate::payouts; use crate::{ - admin, - customers::CustomerId, - enums as api_enums, + admin, enums as api_enums, payments::{self, BankCodeResponse}, }; @@ -57,6 +55,11 @@ pub struct PaymentMethodCreate { /// The card network #[schema(example = "Visa")] pub card_network: Option, + + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -74,6 +77,11 @@ pub struct PaymentMethodUpdate { #[schema(value_type = Option,example = "Visa")] pub card_network: Option, + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, @@ -149,6 +157,11 @@ pub struct PaymentMethodResponse { #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, + + /// Payment method details from locker + #[cfg(feature = "payouts")] + #[schema(value_type = Option)] + pub bank_transfer: Option, } #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -459,8 +472,6 @@ pub struct RequestPaymentMethodTypes { #[derive(Debug, Clone, serde::Serialize, Default, ToSchema)] #[serde(deny_unknown_fields)] pub struct PaymentMethodListRequest { - #[serde(skip_deserializing)] - pub customer_id: Option, /// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK #[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893ein2d")] pub client_secret: Option, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 93c97cbd443..06bd229586d 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -296,8 +296,14 @@ pub struct PaymentsRequest { /// additional data that might be required by hyperswitch pub feature_metadata: Option, - /// payment link object required for generating the payment_link - pub payment_link_object: Option, + + /// Whether to get the payment link (if applicable) + #[schema(default = false, example = true)] + pub payment_link: Option, + + /// custom payment link config for the particular payment + #[schema(value_type = Option)] + pub payment_link_config: Option, /// The business profile to use for this payment, if not passed the default business profile /// associated with the merchant account will be used. @@ -313,15 +319,21 @@ pub struct PaymentsRequest { ///Request for an incremental authorization pub request_incremental_authorization: Option, + + ///Will be used to expire client secret after certain amount of time to be supplied in seconds + ///(900) for 15 mins + #[schema(example = 900)] + pub session_expiry: Option, + + /// additional data related to some frm connectors + pub frm_metadata: 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) - }) + .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) .unwrap_or(0); self.amount .map(|amount| i64::from(amount) + surcharge_amount) @@ -682,7 +694,7 @@ pub struct Card { /// The card holder's name #[schema(value_type = String, example = "John Test")] - pub card_holder_name: Secret, + pub card_holder_name: Option>, /// The CVC number for the card #[schema(value_type = String, example = "242")] @@ -1117,6 +1129,7 @@ pub struct AdditionalCardInfo { pub bank_code: Option, pub last4: Option, pub card_isin: Option, + pub card_extended_bin: Option, pub card_exp_month: Option>, pub card_exp_year: Option>, pub card_holder_name: Option>, @@ -1172,7 +1185,7 @@ pub enum BankRedirectData { }, Eps { /// The billing details for bank redirection - billing_details: BankRedirectBilling, + billing_details: Option, /// The hyperswitch bank code for eps #[schema(value_type = BankNames, example = "triodos_bank")] @@ -1201,7 +1214,7 @@ pub enum BankRedirectData { }, Ideal { /// The billing details for bank redirection - billing_details: BankRedirectBilling, + billing_details: Option, /// The hyperswitch bank code for ideal #[schema(value_type = BankNames, example = "abn_amro")] @@ -1653,6 +1666,7 @@ pub struct CardResponse { pub card_issuer: Option, pub card_issuing_country: Option, pub card_isin: Option, + pub card_extended_bin: Option, pub card_exp_month: Option>, pub card_exp_year: Option>, pub card_holder_name: Option>, @@ -1695,7 +1709,7 @@ pub enum VoucherData { #[serde(rename_all = "snake_case")] pub enum PaymentMethodDataResponse { #[serde(rename = "card")] - Card(CardResponse), + Card(Box), BankTransfer, Wallet, PayLater, @@ -2025,6 +2039,11 @@ pub struct PaymentsResponse { #[schema(example = 100)] pub amount: i64, + /// The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount, + /// If no surcharge_details, net_amount = amount + #[schema(example = 110)] + pub net_amount: i64, + /// The maximum amount that could be captured from the payment #[schema(minimum = 100, example = 6540)] pub amount_capturable: Option, @@ -2257,6 +2276,14 @@ pub struct PaymentsResponse { /// List of incremental authorizations happened to the payment pub incremental_authorizations: Option>, + + /// Date Time expiry of the payment + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub expires_on: Option, + + /// Payment Fingerprint + pub fingerprint: Option, } #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] @@ -2511,6 +2538,7 @@ impl From for CardResponse { card_issuer: card.card_issuer, card_issuing_country: card.card_issuing_country, card_isin: card.card_isin, + card_extended_bin: card.card_extended_bin, card_exp_month: card.card_exp_month, card_exp_year: card.card_exp_year, card_holder_name: card.card_holder_name, @@ -2521,7 +2549,7 @@ impl From for CardResponse { impl From for PaymentMethodDataResponse { fn from(payment_method_data: AdditionalPaymentData) -> Self { match payment_method_data { - AdditionalPaymentData::Card(card) => Self::Card(CardResponse::from(*card)), + AdditionalPaymentData::Card(card) => Self::Card(Box::new(CardResponse::from(*card))), AdditionalPaymentData::PayLater {} => Self::PayLater, AdditionalPaymentData::Wallet {} => Self::Wallet, AdditionalPaymentData::BankRedirect { .. } => Self::BankRedirect, @@ -2598,8 +2626,30 @@ pub struct OrderDetailsWithAmount { pub quantity: u16, /// the amount per quantity of product pub amount: i64, + // Does the order includes shipping + pub requires_shipping: Option, /// The image URL of the product pub product_img_link: Option, + /// ID of the product that is being purchased + pub product_id: Option, + /// Category of the product that is being purchased + pub category: Option, + /// Brand of the product that is being purchased + pub brand: Option, + /// Type of the product that is being purchased + pub product_type: Option, +} + +#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ProductType { + #[default] + Physical, + Digital, + Travel, + Ride, + Event, + Accommodation, } #[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -2610,8 +2660,18 @@ pub struct OrderDetails { /// The quantity of the product to be purchased #[schema(example = 1)] pub quantity: u16, + // Does the order include shipping + pub requires_shipping: Option, /// The image URL of the product pub product_img_link: Option, + /// ID of the product that is being purchased + pub product_id: Option, + /// Category of the product that is being purchased + pub category: Option, + /// Brand of the product that is being purchased + pub brand: Option, + /// Type of the product that is being purchased + pub product_type: Option, } #[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -2971,7 +3031,7 @@ pub struct SecretInfoToInitiateSdk { pub struct ApplePayPaymentRequest { /// The code for country #[schema(value_type = CountryAlpha2, example = "US")] - pub country_code: api_enums::CountryAlpha2, + pub country_code: Option, /// The code for currency #[schema(value_type = Currency, example = "USD")] pub currency_code: api_enums::Currency, @@ -3276,17 +3336,6 @@ mod tests { } } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] -pub struct PaymentLinkObject { - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub link_expiry: Option, - pub merchant_custom_domain_name: Option, - #[schema(value_type = PaymentLinkConfig)] - pub payment_link_config: Option, - /// Custom merchant name for payment link - pub custom_merchant_name: Option, -} - #[derive(Default, Debug, serde::Deserialize, Clone, ToSchema, serde::Serialize)] pub struct RetrievePaymentLinkRequest { pub client_secret: Option, @@ -3306,10 +3355,10 @@ pub struct RetrievePaymentLinkResponse { pub amount: i64, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub link_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub expiry: Option, pub description: Option, - pub status: String, + pub status: PaymentLinkStatus, #[schema(value_type = Option)] pub currency: Option, } @@ -3320,21 +3369,45 @@ pub struct PaymentLinkInitiateRequest { pub payment_id: String, } +#[derive(Debug, serde::Serialize)] +#[serde(untagged)] +pub enum PaymentLinkData { + PaymentLinkDetails(PaymentLinkDetails), + PaymentLinkStatusDetails(PaymentLinkStatusDetails), +} + #[derive(Debug, serde::Serialize)] pub struct PaymentLinkDetails { - pub amount: i64, + pub amount: String, pub currency: api_enums::Currency, pub pub_key: String, pub client_secret: String, pub payment_id: String, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, pub merchant_logo: String, pub return_url: String, pub merchant_name: String, - pub order_details: Option>, + pub order_details: Option>, pub max_items_visible_after_collapse: i8, - pub sdk_theme: Option, + pub theme: String, + pub merchant_description: Option, + pub sdk_layout: String, +} + +#[derive(Debug, serde::Serialize)] +pub struct PaymentLinkStatusDetails { + pub amount: String, + pub currency: api_enums::Currency, + pub payment_id: String, + pub merchant_logo: String, + pub merchant_name: String, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + pub intent_status: api_enums::IntentStatus, + pub payment_link_status: PaymentLinkStatus, + pub error_code: Option, + pub error_message: Option, } #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] @@ -3390,3 +3463,31 @@ pub struct PaymentLinkListResponse { // The list of payment link response objects pub data: Vec, } + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentCreatePaymentLinkConfig { + #[serde(flatten)] + #[schema(value_type = Option)] + pub config: admin::PaymentLinkConfigRequest, +} + +#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct OrderDetailsWithStringAmount { + /// Name of the product that is being purchased + #[schema(max_length = 255, example = "shirt")] + pub product_name: String, + /// The quantity of the product to be purchased + #[schema(example = 1)] + pub quantity: u16, + /// the amount per quantity of product + pub amount: String, + /// Product Image link + pub product_img_link: Option, +} + +#[derive(PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum PaymentLinkStatus { + Active, + Expired, +} diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index f7dba2446e9..9e771b47121 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -181,7 +181,7 @@ pub struct Card { /// The card holder's name #[schema(value_type = String, example = "John Doe")] - pub card_holder_name: Secret, + pub card_holder_name: Option>, } #[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] @@ -195,16 +195,16 @@ pub enum Bank { #[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct AchBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// Bank account number is an unique identifier assigned by a bank to a customer. #[schema(value_type = String, example = "000123456")] @@ -218,16 +218,16 @@ pub struct AchBankTransfer { #[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct BacsBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// Bank account number is an unique identifier assigned by a bank to a customer. #[schema(value_type = String, example = "000123456")] @@ -242,16 +242,16 @@ pub struct BacsBankTransfer { // The SEPA (Single Euro Payments Area) is a pan-European network that allows you to send and receive payments in euros between two cross-border bank accounts in the eurozone. pub struct SepaBankTransfer { /// Bank name - #[schema(value_type = String, example = "Deutsche Bank")] - pub bank_name: String, + #[schema(value_type = Option, example = "Deutsche Bank")] + pub bank_name: Option, /// Bank country code - #[schema(value_type = CountryAlpha2, example = "US")] - pub bank_country_code: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + pub bank_country_code: Option, /// Bank city - #[schema(value_type = String, example = "California")] - pub bank_city: String, + #[schema(value_type = Option, example = "California")] + pub bank_city: Option, /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer. #[schema(value_type = String, example = "DE89370400440532013000")] diff --git a/crates/api_models/src/recon.rs b/crates/api_models/src/recon.rs new file mode 100644 index 00000000000..efbe28f96ba --- /dev/null +++ b/crates/api_models/src/recon.rs @@ -0,0 +1,21 @@ +use common_utils::pii; +use masking::Secret; + +use crate::enums; + +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct ReconUpdateMerchantRequest { + pub merchant_id: String, + pub recon_status: enums::ReconStatus, + pub user_email: pii::Email, +} + +#[derive(Debug, serde::Serialize)] +pub struct ReconTokenResponse { + pub token: Secret, +} + +#[derive(Debug, serde::Serialize)] +pub struct ReconStatusResponse { + pub recon_status: enums::ReconStatus, +} diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index e89de9c5893..1a0668023f0 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -127,7 +127,10 @@ pub struct RefundResponse { /// The connector used for the refund and the corresponding payment #[schema(example = "stripe")] pub connector: String, + /// The id of business profile for this refund pub profile_id: Option, + /// The merchant_connector_id of the processor through which this payment went through + pub merchant_connector_id: Option, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 10d8411f8e7..a04c4fef660 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -1,4 +1,4 @@ -use common_utils::pii; +use common_utils::{crypto::OptionalEncryptableName, pii}; use masking::Secret; use crate::user_role::UserStatus; @@ -86,6 +86,7 @@ pub struct InviteUserRequest { #[derive(Debug, serde::Serialize)] pub struct InviteUserResponse { pub is_email_sent: bool, + pub password: Option>, } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -128,3 +129,21 @@ pub struct VerifyEmailRequest { } pub type VerifyEmailResponse = DashboardEntryResponse; + +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct SendVerifyEmailRequest { + pub email: pii::Email, +} + +#[derive(Debug, serde::Serialize)] +pub struct UserMerchantAccount { + pub merchant_id: String, + pub merchant_name: OptionalEncryptableName, +} + +#[cfg(feature = "recon")] +#[derive(serde::Serialize, Debug)] +pub struct VerifyTokenResponse { + pub merchant_id: String, + pub user_email: pii::Email, +} diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index 735cd240b6e..72fca2b2f08 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -32,6 +32,8 @@ pub enum Permission { DisputeWrite, MandateRead, MandateWrite, + CustomerRead, + CustomerWrite, FileRead, FileWrite, Analytics, @@ -53,6 +55,7 @@ pub enum PermissionModule { Routing, Analytics, Mandates, + Customer, Disputes, Files, ThreeDsDecisionManager, diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index bc8e75f6d47..7b3564732bf 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -8,11 +8,18 @@ use crate::{disputes, enums as api_enums, mandates, payments, refunds}; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)] #[serde(rename_all = "snake_case")] pub enum IncomingWebhookEvent { + /// Authorization + Capture success PaymentIntentFailure, + /// Authorization + Capture failure PaymentIntentSuccess, PaymentIntentProcessing, PaymentIntentPartiallyFunded, PaymentIntentCancelled, + PaymentIntentCancelFailure, + PaymentIntentAuthorizationSuccess, + PaymentIntentAuthorizationFailure, + PaymentIntentCaptureSuccess, + PaymentIntentCaptureFailure, PaymentActionRequired, EventNotSupported, SourceChargeable, @@ -86,7 +93,12 @@ impl From for WebhookFlow { | IncomingWebhookEvent::PaymentIntentProcessing | IncomingWebhookEvent::PaymentActionRequired | IncomingWebhookEvent::PaymentIntentPartiallyFunded - | IncomingWebhookEvent::PaymentIntentCancelled => Self::Payment, + | IncomingWebhookEvent::PaymentIntentCancelled + | IncomingWebhookEvent::PaymentIntentCancelFailure + | IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + | IncomingWebhookEvent::PaymentIntentAuthorizationFailure + | IncomingWebhookEvent::PaymentIntentCaptureSuccess + | IncomingWebhookEvent::PaymentIntentCaptureFailure => Self::Payment, IncomingWebhookEvent::EventNotSupported => Self::ReturnResponse, IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => { Self::Refund diff --git a/crates/cards/Cargo.toml b/crates/cards/Cargo.toml index 00445936e3d..6892350ce6f 100644 --- a/crates/cards/Cargo.toml +++ b/crates/cards/Cargo.toml @@ -12,7 +12,7 @@ license.workspace = true [dependencies] error-stack = "0.3.1" luhn = "1.0.1" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.40" time = "0.3.21" @@ -20,5 +20,8 @@ time = "0.3.21" common_utils = { version = "0.1.0", path = "../common_utils" } masking = { version = "0.1.0", path = "../masking" } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } + [dev-dependencies] -serde_json = "1.0.96" +serde_json = "1.0.108" diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index d083a420a1e..0bb07b83dc6 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -1,6 +1,8 @@ use std::{fmt, ops::Deref, str::FromStr}; use masking::{PeekInterface, Strategy, StrongSecret, WithType}; +#[cfg(not(target_arch = "wasm32"))] +use router_env::logger; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; @@ -22,6 +24,13 @@ impl CardNumber { pub fn get_card_isin(self) -> String { self.0.peek().chars().take(6).collect::() } + + pub fn get_extended_card_bin(self) -> String { + self.0.peek().chars().take(8).collect::() + } + pub fn get_card_no(self) -> String { + self.0.peek().chars().collect::() + } pub fn get_last4(self) -> String { self.0 .peek() @@ -33,6 +42,9 @@ impl CardNumber { .rev() .collect::() } + pub fn get_card_extended_bin(self) -> String { + self.0.peek().chars().take(8).collect::() + } } impl FromStr for CardNumber { @@ -85,7 +97,13 @@ where return WithType::fmt(val, f); } - write!(f, "{}{}", &val_str[..6], "*".repeat(val_str.len() - 6)) + if let Some(value) = val_str.get(..6) { + write!(f, "{}{}", value, "*".repeat(val_str.len() - 6)) + } else { + #[cfg(not(target_arch = "wasm32"))] + logger::error!("Invalid card number {val_str}"); + WithType::fmt(val, f) + } } } diff --git a/crates/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index 72d9f6bb0bb..3ed01ca2a97 100644 --- a/crates/common_enums/Cargo.toml +++ b/crates/common_enums/Cargo.toml @@ -12,8 +12,8 @@ dummy_connector = [] [dependencies] diesel = { version = "2.1.0", features = ["postgres"] } -serde = { version = "1.0.160", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" strum = { version = "0.25", features = ["derive"] } utoipa = { version = "3.3.0", features = ["preserve_order"] } @@ -21,4 +21,4 @@ utoipa = { version = "3.3.0", features = ["preserve_order"] } router_derive = { version = "0.1.0", path = "../router_derive" } [dev-dependencies] -serde_json = "1.0.96" +serde_json = "1.0.108" diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 980f98db151..949cc2e0034 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -6,12 +6,13 @@ use utoipa::ToSchema; pub mod diesel_exports { pub use super::{ DbAttemptStatus as AttemptStatus, DbAuthenticationType as AuthenticationType, - DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus, - DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, - DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventType as EventType, - DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus, - DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, - DbPaymentType as PaymentType, DbRefundStatus as RefundStatus, + DbBlocklistDataKind as BlocklistDataKind, DbCaptureMethod as CaptureMethod, + DbCaptureStatus as CaptureStatus, DbConnectorType as ConnectorType, + DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, DbDisputeStage as DisputeStage, + DbDisputeStatus as DisputeStatus, DbEventType as EventType, DbFutureUsage as FutureUsage, + DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus, + DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentType as PaymentType, + DbRefundStatus as RefundStatus, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, }; } @@ -142,9 +143,11 @@ pub enum RoutableConnectors { Payme, Paypal, Payu, + Placetopay, Powertranz, Prophetpay, Rapyd, + Riskified, Shift4, Signifyd, Square, @@ -273,6 +276,27 @@ pub enum AuthorizationStatus { Unresolved, } +#[derive( + Clone, + Debug, + PartialEq, + Eq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum BlocklistDataKind { + PaymentMethod, + CardBin, + ExtendedCardBin, +} + #[derive( Clone, Copy, @@ -919,10 +943,14 @@ impl Currency { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum EventType { + /// Authorize + Capture success PaymentSucceeded, + /// Authorize + Capture failed PaymentFailed, PaymentProcessing, PaymentCancelled, + PaymentAuthorized, + PaymentCaptured, ActionRequired, RefundSucceeded, RefundFailed, diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 3619c93d772..739129d02db 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -30,8 +30,8 @@ regex = "1.8.4" reqwest = { version = "0.11.18", features = ["json", "native-tls", "gzip", "multipart"] } ring = { version = "0.16.20", features = ["std"] } rustc-hash = "1.1.0" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" serde_urlencoded = "0.7.1" signal-hook = { version = "0.3.15", optional = true } strum = { version = "0.24.1", features = ["derive"] } diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 7f9533d7ead..cd24e430b76 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -33,11 +33,8 @@ pub const SURCHARGE_PERCENTAGE_PRECISION_LENGTH: u8 = 2; /// Header Key for application overhead of a request pub const X_HS_LATENCY: &str = "x-hs-latency"; -/// SDK Default Theme const -pub const DEFAULT_SDK_THEME: &str = "#7EA8F6"; - /// Default Payment Link Background color -pub const DEFAULT_BACKGROUND_COLOR: &str = "#E5E5E5"; +pub const DEFAULT_BACKGROUND_COLOR: &str = "#212E46"; /// Default product Img Link pub const DEFAULT_PRODUCT_IMG: &str = "https://i.imgur.com/On3VtKF.png"; @@ -50,3 +47,9 @@ pub const PROPHETPAY_REDIRECT_URL: &str = "https://ccm-thirdparty.cps.golf/hp/to /// Variable which store the card token for Prophetpay pub const PROPHETPAY_TOKEN: &str = "cctoken"; + +/// Default SDK Layout +pub const DEFAULT_SDK_LAYOUT: &str = "tabs"; + +/// Payment intent default client secret expiry (in seconds) +pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60; diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index a0f365dd40d..c2c83a3589e 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -279,7 +279,10 @@ impl DecodeMessage for GcmAes256 { .change_context(errors::CryptoError::DecodingFailed)?; let nonce_sequence = NonceSequence::from_bytes( - msg[..ring::aead::NONCE_LEN] + msg.get(..ring::aead::NONCE_LEN) + .ok_or(errors::CryptoError::DecodingFailed) + .into_report() + .attach_printable("Failed to read the nonce form the encrypted ciphertext")? .try_into() .into_report() .change_context(errors::CryptoError::DecodingFailed)?, diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index c9efbb73c20..c2bf50d96c3 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -40,12 +40,16 @@ pub enum ApiEventsType { }, Routing, ResourceListAPI, - PaymentRedirectionResponse, + PaymentRedirectionResponse { + connector: Option, + payment_id: Option, + }, Gsm, // TODO: This has to be removed once the corresponding apiEventTypes are created Miscellaneous, RustLocker, FraudCheck, + Recon, } impl ApiEventMetric for serde_json::Value {} diff --git a/crates/common_utils/src/pii.rs b/crates/common_utils/src/pii.rs index 39793de5c2b..066962af1d4 100644 --- a/crates/common_utils/src/pii.rs +++ b/crates/common_utils/src/pii.rs @@ -12,6 +12,8 @@ use diesel::{ }; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, Secret, Strategy, WithType}; +#[cfg(feature = "logs")] +use router_env::logger; use crate::{ crypto::Encryptable, @@ -41,13 +43,14 @@ where fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { let val_str: &str = val.as_ref(); - // masks everything but the last 4 digits - write!( - f, - "{}{}", - "*".repeat(val_str.len() - 4), - &val_str[val_str.len() - 4..] - ) + if let Some(val_str) = val_str.get(val_str.len() - 4..) { + // masks everything but the last 4 digits + write!(f, "{}{}", "*".repeat(val_str.len() - 4), val_str) + } else { + #[cfg(feature = "logs")] + logger::error!("Invalid phone number: {val_str}"); + WithType::fmt(val, f) + } } } @@ -174,16 +177,26 @@ where { return WithType::fmt(val, f); } - write!( - f, - "{}_{}_{}", - client_secret_segments[0], - client_secret_segments[1], - "*".repeat( - val_str.len() - - (client_secret_segments[0].len() + client_secret_segments[1].len() + 2) + + if let Some((client_secret_segments_0, client_secret_segments_1)) = client_secret_segments + .first() + .zip(client_secret_segments.get(1)) + { + write!( + f, + "{}_{}_{}", + client_secret_segments_0, + client_secret_segments_1, + "*".repeat( + val_str.len() + - (client_secret_segments_0.len() + client_secret_segments_1.len() + 2) + ) ) - ) + } else { + #[cfg(feature = "logs")] + logger::error!("Invalid client secret: {val_str}"); + WithType::fmt(val, f) + } } } @@ -231,12 +244,6 @@ impl TryFrom for Email { } } -impl From> for Email { - fn from(value: Secret) -> Self { - Self(value) - } -} - impl ops::Deref for Email { type Target = Secret; @@ -325,7 +332,13 @@ where } } - write!(f, "{}.**.**.**", segments[0]) + if let Some(segments) = segments.first() { + write!(f, "{}.**.**.**", segments) + } else { + #[cfg(feature = "logs")] + logger::error!("Invalid IP address: {val_str}"); + WithType::fmt(val, f) + } } } diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index d6d9281a4a0..f063a1940be 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -25,6 +25,7 @@ pub enum ContentType { Json, FormUrlEncoded, FormData, + Xml, } fn default_request_headers() -> [(String, Maskable); 1] { @@ -37,12 +38,28 @@ fn default_request_headers() -> [(String, Maskable); 1] { pub struct Request { pub url: String, pub headers: Headers, - pub payload: Option>, pub method: Method, - pub content_type: Option, pub certificate: Option, pub certificate_key: Option, - pub form_data: Option, + pub body: Option, +} + +impl std::fmt::Debug for RequestContent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Json(_) => "JsonRequestBody", + Self::FormUrlEncoded(_) => "FormUrlEncodedRequestBody", + Self::FormData(_) => "FormDataRequestBody", + Self::Xml(_) => "XmlRequestBody", + }) + } +} + +pub enum RequestContent { + Json(Box), + FormUrlEncoded(Box), + FormData(reqwest::multipart::Form), + Xml(Box), } impl Request { @@ -51,16 +68,14 @@ impl Request { method, url: String::from(url), headers: std::collections::HashSet::new(), - payload: None, - content_type: None, certificate: None, certificate_key: None, - form_data: None, + body: None, } } - pub fn set_body(&mut self, body: String) { - self.payload = Some(body.into()); + pub fn set_body>(&mut self, body: T) { + self.body.replace(body.into()); } pub fn add_default_headers(&mut self) { @@ -71,10 +86,6 @@ impl Request { self.headers.insert((String::from(header), value)); } - pub fn add_content_type(&mut self, content_type: ContentType) { - self.content_type = Some(content_type); - } - pub fn add_certificate(&mut self, certificate: Option) { self.certificate = certificate; } @@ -82,22 +93,16 @@ impl Request { pub fn add_certificate_key(&mut self, certificate_key: Option) { self.certificate = certificate_key; } - - pub fn set_form_data(&mut self, form_data: reqwest::multipart::Form) { - self.form_data = Some(form_data); - } } #[derive(Debug)] pub struct RequestBuilder { pub url: String, pub headers: Headers, - pub payload: Option>, pub method: Method, - pub content_type: Option, pub certificate: Option, pub certificate_key: Option, - pub form_data: Option, + pub body: Option, } impl RequestBuilder { @@ -106,11 +111,9 @@ impl RequestBuilder { method: Method::Get, url: String::with_capacity(1024), headers: std::collections::HashSet::new(), - payload: None, - content_type: None, certificate: None, certificate_key: None, - form_data: None, + body: None, } } @@ -135,23 +138,12 @@ impl RequestBuilder { } pub fn headers(mut self, headers: Vec<(String, Maskable)>) -> Self { - let mut h = headers.into_iter().map(|(h, v)| (h, v)); - self.headers.extend(&mut h); + self.headers.extend(headers); self } - pub fn form_data(mut self, form_data: Option) -> Self { - self.form_data = form_data; - self - } - - pub fn body(mut self, option_body: Option) -> Self { - self.payload = option_body.map(RequestBody::get_inner_value); - self - } - - pub fn content_type(mut self, content_type: ContentType) -> Self { - self.content_type = Some(content_type); + pub fn set_body>(mut self, body: T) -> Self { + self.body.replace(body.into()); self } @@ -170,11 +162,9 @@ impl RequestBuilder { method: self.method, url: self.url, headers: self.headers, - payload: self.payload, - content_type: self.content_type, certificate: self.certificate, certificate_key: self.certificate_key, - form_data: self.form_data, + body: self.body, } } } @@ -201,7 +191,15 @@ impl RequestBody { logger::info!(connector_request_body=?body); Ok(Self(Secret::new(encoder(body)?))) } - pub fn get_inner_value(request_body: Self) -> Secret { - request_body.0 + + pub fn get_inner_value(request_body: RequestContent) -> Secret { + match request_body { + RequestContent::Json(i) => serde_json::to_string(&i).unwrap_or_default().into(), + RequestContent::FormUrlEncoded(i) => { + serde_urlencoded::to_string(&i).unwrap_or_default().into() + } + RequestContent::Xml(i) => quick_xml::se::to_string(&i).unwrap_or_default().into(), + RequestContent::FormData(_) => String::new().into(), + } } } diff --git a/crates/config_importer/Cargo.toml b/crates/config_importer/Cargo.toml new file mode 100644 index 00000000000..d831812cdca --- /dev/null +++ b/crates/config_importer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "config_importer" +description = "Utility to convert a TOML configuration file to a list of environment variables" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +readme = "README.md" +license.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +anyhow = "1.0.75" +clap = { version = "4.3.2", default-features = false, features = ["std", "derive", "help", "usage"] } +indexmap = { version = "2.1.0", optional = true } +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +toml = { version = "0.7.4", default-features = false, features = ["parse"] } + +[features] +default = ["preserve_order"] +preserve_order = ["dep:indexmap", "serde_json/preserve_order", "toml/preserve_order"] diff --git a/crates/config_importer/README.md b/crates/config_importer/README.md new file mode 100644 index 00000000000..a7190e6441b --- /dev/null +++ b/crates/config_importer/README.md @@ -0,0 +1,41 @@ +# config_importer + +A simple utility tool to import a Hyperswitch TOML configuration file, convert +it into environment variable key-value pairs, and export it in the specified +format. +As of now, it supports only exporting the environment variables to a JSON format +compatible with Kubernetes, but it can be easily extended to export to a YAML +format compatible with Kubernetes or the env file format. + +## Usage + +You can find the usage information from the help message by specifying the +`--help` flag: + +```shell +cargo run --bin config_importer -- --help +``` + +### Specifying the output location + +If the `--output-file` flag is not specified, the utility prints the output to +stdout. +If you would like to write the output to a file instead, you can specify the +`--output-file` flag with the path to the output file: + +```shell +cargo run --bin config_importer -- --input-file config/development.toml --output-file config/development.json +``` + +### Specifying a different prefix + +If the `--prefix` flag is not specified, the default prefix `ROUTER` is +considered, which generates the environment variables accepted by the `router` +binary/application. +If you'd want to generate environment variables for the `drainer` +binary/application, then you can specify the `--prefix` flag with value +`drainer` (or `DRAINER`, both work). + +```shell +cargo run --bin config_importer -- --input-file config/drainer.toml --prefix drainer +``` diff --git a/crates/config_importer/src/cli.rs b/crates/config_importer/src/cli.rs new file mode 100644 index 00000000000..d394f51b4b3 --- /dev/null +++ b/crates/config_importer/src/cli.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; + +/// Utility to import a hyperswitch TOML configuration file, convert it into environment variable +/// key-value pairs, and export it in the specified format. +#[derive(clap::Parser, Debug)] +#[command(arg_required_else_help = true)] +pub(crate) struct Args { + /// Input TOML configuration file. + #[arg(short, long, value_name = "FILE")] + pub(crate) input_file: PathBuf, + + /// The format to convert the environment variables to. + #[arg( + value_enum, + short = 'f', + long, + value_name = "FORMAT", + default_value = "kubernetes-json" + )] + pub(crate) output_format: OutputFormat, + + /// Output file. Output will be written to stdout if not specified. + #[arg(short, long, value_name = "FILE")] + pub(crate) output_file: Option, + + /// Prefix to be used for each environment variable in the generated output. + #[arg(short, long, default_value = "ROUTER")] + pub(crate) prefix: String, +} + +/// The output format to convert environment variables to. +#[derive(clap::ValueEnum, Clone, Copy, Debug)] +pub(crate) enum OutputFormat { + /// Converts each environment variable to an object containing `name` and `value` fields. + /// + /// ```json + /// { + /// "name": "ENVIRONMENT", + /// "value": "PRODUCTION" + /// } + /// ``` + KubernetesJson, +} diff --git a/crates/config_importer/src/main.rs b/crates/config_importer/src/main.rs new file mode 100644 index 00000000000..2a29ad56dc7 --- /dev/null +++ b/crates/config_importer/src/main.rs @@ -0,0 +1,98 @@ +mod cli; + +use std::io::{BufWriter, Write}; + +use anyhow::Context; + +/// The separator used in environment variable names. +const ENV_VAR_SEPARATOR: &str = "__"; + +#[cfg(not(feature = "preserve_order"))] +type EnvironmentVariableMap = std::collections::HashMap; + +#[cfg(feature = "preserve_order")] +type EnvironmentVariableMap = indexmap::IndexMap; + +fn main() -> anyhow::Result<()> { + let args = ::parse(); + + // Read input TOML file + let toml_contents = + std::fs::read_to_string(args.input_file).context("Failed to read input file")?; + let table = toml_contents + .parse::() + .context("Failed to parse TOML file contents")?; + + // Parse TOML file contents to a `HashMap` of environment variable name and value pairs + let env_vars = table + .iter() + .flat_map(|(key, value)| process_toml_value(&args.prefix, key, value)) + .collect::(); + + let writer: BufWriter> = match args.output_file { + // Write to file if output file is specified + Some(file) => BufWriter::new(Box::new( + std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file) + .context("Failed to open output file")?, + )), + // Write to stdout otherwise + None => BufWriter::new(Box::new(std::io::stdout().lock())), + }; + + // Write environment variables in specified format + match args.output_format { + cli::OutputFormat::KubernetesJson => { + let k8s_env_vars = env_vars + .into_iter() + .map(|(name, value)| KubernetesEnvironmentVariable { name, value }) + .collect::>(); + serde_json::to_writer_pretty(writer, &k8s_env_vars) + .context("Failed to serialize environment variables as JSON")? + } + } + + Ok(()) +} + +fn process_toml_value( + prefix: impl std::fmt::Display + Clone, + key: impl std::fmt::Display + Clone, + value: &toml::Value, +) -> Vec<(String, String)> { + let key_with_prefix = format!("{prefix}{ENV_VAR_SEPARATOR}{key}").to_ascii_uppercase(); + + match value { + toml::Value::String(s) => vec![(key_with_prefix, s.to_owned())], + toml::Value::Integer(i) => vec![(key_with_prefix, i.to_string())], + toml::Value::Float(f) => vec![(key_with_prefix, f.to_string())], + toml::Value::Boolean(b) => vec![(key_with_prefix, b.to_string())], + toml::Value::Datetime(dt) => vec![(key_with_prefix, dt.to_string())], + toml::Value::Array(values) => { + if values.is_empty() { + return vec![(key_with_prefix, String::new())]; + } + + // This logic does not support / account for arrays of tables or arrays of arrays. + let (_processed_keys, processed_values) = values + .iter() + .flat_map(|v| process_toml_value(prefix.clone(), key.clone(), v)) + .unzip::<_, _, Vec, Vec>(); + vec![(key_with_prefix, processed_values.join(","))] + } + toml::Value::Table(map) => map + .into_iter() + .flat_map(|(k, v)| process_toml_value(key_with_prefix.clone(), k, v)) + .collect(), + } +} + +/// The Kubernetes environment variable structure containing a name and a value. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub(crate) struct KubernetesEnvironmentVariable { + name: String, + value: String, +} diff --git a/crates/connector_configs/Cargo.toml b/crates/connector_configs/Cargo.toml new file mode 100644 index 00000000000..083a741ef50 --- /dev/null +++ b/crates/connector_configs/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "connector_configs" +description = "Connector Integration Dashboard" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[features] +default = ["payouts", "dummy_connector"] +production = [] +development = [] +sandbox = [] +dummy_connector = ["api_models/dummy_connector", "development"] +payouts = [] + +[dependencies] +api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } +serde = { version = "1.0.193", features = ["derive"] } +serde_with = "3.4.0" +toml = "0.7.3" +utoipa = { version = "3.3.0", features = ["preserve_order"] } \ No newline at end of file diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs new file mode 100644 index 00000000000..c3cbaab4ab3 --- /dev/null +++ b/crates/connector_configs/src/common_config.rs @@ -0,0 +1,176 @@ +use api_models::{payment_methods, payments}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(rename_all = "snake_case")] +pub struct ZenApplePay { + pub terminal_uuid: Option, + pub pay_wall_secret: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(untagged)] +pub enum ApplePayData { + ApplePay(payments::ApplePayMetadata), + ApplePayCombined(payments::ApplePayCombinedMetadata), + Zen(ZenApplePay), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GpayDashboardPayLoad { + #[serde(skip_serializing_if = "Option::is_none")] + pub gateway_merchant_id: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "stripe:version")] + pub stripe_version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename( + serialize = "stripe_publishable_key", + deserialize = "stripe:publishable_key" + ))] + #[serde(alias = "stripe:publishable_key")] + #[serde(alias = "stripe_publishable_key")] + pub stripe_publishable_key: Option, + pub merchant_name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub merchant_id: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(rename_all = "snake_case")] +pub struct ZenGooglePay { + pub terminal_uuid: Option, + pub pay_wall_secret: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(untagged)] +pub enum GooglePayData { + Standard(GpayDashboardPayLoad), + Zen(ZenGooglePay), +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(untagged)] +pub enum GoogleApiModelData { + Standard(payments::GpayMetaData), + Zen(ZenGooglePay), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct PaymentMethodsEnabled { + pub payment_method: api_models::enums::PaymentMethod, + pub payment_method_types: Option>, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct ApiModelMetaData { + pub merchant_config_currency: Option, + pub merchant_account_id: Option, + pub account_name: Option, + pub terminal_id: Option, + pub merchant_id: Option, + pub google_pay: Option, + pub apple_pay: Option, + pub apple_pay_combined: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct CardProvider { + pub payment_method_type: api_models::enums::CardNetwork, + /// List of currencies accepted or has the processing capabilities of the processor + #[schema(example = json!( + { + "type": "specific_accepted", + "list": ["USD", "INR"] + } + ), value_type = Option)] + pub accepted_currencies: Option, + #[schema(example = json!( + { + "type": "specific_accepted", + "list": ["UK", "AU"] + } + ), value_type = Option)] + pub accepted_countries: Option, +} +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct Provider { + pub payment_method_type: api_models::enums::PaymentMethodType, + /// List of currencies accepted or has the processing capabilities of the processor + #[schema(example = json!( + { + "type": "specific_accepted", + "list": ["USD", "INR"] + } + ), value_type = Option)] + pub accepted_currencies: Option, + #[schema(example = json!( + { + "type": "specific_accepted", + "list": ["UK", "AU"] + } + ), value_type = Option)] + pub accepted_countries: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct ConnectorApiIntegrationPayload { + pub connector_type: String, + pub profile_id: String, + pub connector_name: api_models::enums::Connector, + #[serde(skip_deserializing)] + #[schema(example = "stripe_US_travel")] + pub connector_label: Option, + pub merchant_connector_id: Option, + pub disabled: bool, + pub test_mode: bool, + pub payment_methods_enabled: Option>, + pub metadata: Option, + pub connector_webhook_details: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct DashboardPaymentMethodPayload { + pub payment_method: api_models::enums::PaymentMethod, + pub payment_method_type: String, + pub provider: Option>, + pub card_provider: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct DashboardRequestPayload { + pub connector: api_models::enums::Connector, + pub payment_methods_enabled: Option>, + pub metadata: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(rename_all = "snake_case")] +pub struct DashboardMetaData { + pub merchant_config_currency: Option, + pub merchant_account_id: Option, + pub account_name: Option, + pub terminal_id: Option, + pub merchant_id: Option, + pub google_pay: Option, + pub apple_pay: Option, + pub apple_pay_combined: Option, +} diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs new file mode 100644 index 00000000000..f0997d53107 --- /dev/null +++ b/crates/connector_configs/src/connector.rs @@ -0,0 +1,274 @@ +use std::collections::HashMap; + +#[cfg(feature = "payouts")] +use api_models::enums::PayoutConnectors; +use api_models::{enums::Connector, payments}; +use serde::Deserialize; +#[cfg(any(feature = "sandbox", feature = "development", feature = "production"))] +use toml; + +use crate::common_config::{CardProvider, GooglePayData, Provider, ZenApplePay}; + +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Classic { + pub password_classic: String, + pub username_classic: String, + pub merchant_id_classic: String, +} + +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Evoucher { + pub password_evoucher: String, + pub username_evoucher: String, + pub merchant_id_evoucher: String, +} + +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct CurrencyAuthKeyType { + pub classic: Classic, + pub evoucher: Evoucher, +} + +#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum ConnectorAuthType { + HeaderKey { + api_key: String, + }, + BodyKey { + api_key: String, + key1: String, + }, + SignatureKey { + api_key: String, + key1: String, + api_secret: String, + }, + MultiAuthKey { + api_key: String, + key1: String, + api_secret: String, + key2: String, + }, + CurrencyAuthKey { + auth_key_map: HashMap, + }, + #[default] + NoKey, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +#[serde(untagged)] +pub enum ApplePayTomlConfig { + Standard(payments::ApplePayMetadata), + Zen(ZenApplePay), +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +pub struct ConfigMetadata { + pub merchant_config_currency: Option, + pub merchant_account_id: Option, + pub account_name: Option, + pub terminal_id: Option, + pub google_pay: Option, + pub apple_pay: Option, + pub merchant_id: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +pub struct ConnectorTomlConfig { + pub connector_auth: Option, + pub connector_webhook_details: Option, + pub metadata: Option, + pub credit: Option>, + pub debit: Option>, + pub bank_transfer: Option>, + pub bank_redirect: Option>, + pub bank_debit: Option>, + pub pay_later: Option>, + pub wallet: Option>, + pub crypto: Option>, + pub reward: Option>, + pub upi: Option>, + pub voucher: Option>, + pub gift_card: Option>, + pub card_redirect: Option>, + pub is_verifiable: Option, +} +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +pub struct ConnectorConfig { + pub aci: Option, + pub adyen: Option, + #[cfg(feature = "payouts")] + pub adyen_payout: Option, + pub airwallex: Option, + pub authorizedotnet: Option, + pub bankofamerica: Option, + pub bitpay: Option, + pub bluesnap: Option, + pub boku: Option, + pub braintree: Option, + pub cashtocode: Option, + pub checkout: Option, + pub coinbase: Option, + pub cryptopay: Option, + pub cybersource: Option, + pub iatapay: Option, + pub opennode: Option, + pub bambora: Option, + pub dlocal: Option, + pub fiserv: Option, + pub forte: Option, + pub globalpay: Option, + pub globepay: Option, + pub gocardless: Option, + pub helcim: Option, + pub klarna: Option, + pub mollie: Option, + pub multisafepay: Option, + pub nexinets: Option, + pub nmi: Option, + pub noon: Option, + pub nuvei: Option, + pub payme: Option, + pub paypal: Option, + pub payu: Option, + pub placetopay: Option, + pub plaid: Option, + pub powertranz: Option, + pub prophetpay: Option, + pub riskified: Option, + pub rapyd: Option, + pub shift4: Option, + pub stripe: Option, + pub signifyd: Option, + pub trustpay: Option, + pub tsys: Option, + pub volt: Option, + #[cfg(feature = "payouts")] + pub wise_payout: Option, + pub worldline: Option, + pub worldpay: Option, + pub zen: Option, + pub square: Option, + pub stax: Option, + pub dummy_connector: Option, + pub stripe_test: Option, + pub paypal_test: Option, +} + +impl ConnectorConfig { + fn new() -> Result { + #[cfg(all( + feature = "production", + not(any(feature = "sandbox", feature = "development")) + ))] + let config = toml::from_str::(include_str!("../toml/production.toml")); + #[cfg(all( + feature = "sandbox", + not(any(feature = "production", feature = "development")) + ))] + let config = toml::from_str::(include_str!("../toml/sandbox.toml")); + #[cfg(feature = "development")] + let config = toml::from_str::(include_str!("../toml/development.toml")); + + #[cfg(not(any(feature = "sandbox", feature = "development", feature = "production")))] + return Err(String::from( + "Atleast one features has to be enabled for connectorconfig", + )); + + #[cfg(any(feature = "sandbox", feature = "development", feature = "production"))] + match config { + Ok(data) => Ok(data), + Err(err) => Err(err.to_string()), + } + } + + #[cfg(feature = "payouts")] + pub fn get_payout_connector_config( + connector: PayoutConnectors, + ) -> Result, String> { + let connector_data = Self::new()?; + match connector { + PayoutConnectors::Adyen => Ok(connector_data.adyen_payout), + PayoutConnectors::Wise => Ok(connector_data.wise_payout), + } + } + + pub fn get_connector_config( + connector: Connector, + ) -> Result, String> { + let connector_data = Self::new()?; + match connector { + Connector::Aci => Ok(connector_data.aci), + Connector::Adyen => Ok(connector_data.adyen), + Connector::Airwallex => Ok(connector_data.airwallex), + Connector::Authorizedotnet => Ok(connector_data.authorizedotnet), + Connector::Bankofamerica => Ok(connector_data.bankofamerica), + Connector::Bitpay => Ok(connector_data.bitpay), + Connector::Bluesnap => Ok(connector_data.bluesnap), + Connector::Boku => Ok(connector_data.boku), + Connector::Braintree => Ok(connector_data.braintree), + Connector::Cashtocode => Ok(connector_data.cashtocode), + Connector::Checkout => Ok(connector_data.checkout), + Connector::Coinbase => Ok(connector_data.coinbase), + Connector::Cryptopay => Ok(connector_data.cryptopay), + Connector::Cybersource => Ok(connector_data.cybersource), + Connector::Iatapay => Ok(connector_data.iatapay), + Connector::Opennode => Ok(connector_data.opennode), + Connector::Bambora => Ok(connector_data.bambora), + Connector::Dlocal => Ok(connector_data.dlocal), + Connector::Fiserv => Ok(connector_data.fiserv), + Connector::Forte => Ok(connector_data.forte), + Connector::Globalpay => Ok(connector_data.globalpay), + Connector::Globepay => Ok(connector_data.globepay), + Connector::Gocardless => Ok(connector_data.gocardless), + Connector::Helcim => Ok(connector_data.helcim), + Connector::Klarna => Ok(connector_data.klarna), + Connector::Mollie => Ok(connector_data.mollie), + Connector::Multisafepay => Ok(connector_data.multisafepay), + Connector::Nexinets => Ok(connector_data.nexinets), + Connector::Prophetpay => Ok(connector_data.prophetpay), + Connector::Nmi => Ok(connector_data.nmi), + Connector::Noon => Ok(connector_data.noon), + Connector::Nuvei => Ok(connector_data.nuvei), + Connector::Payme => Ok(connector_data.payme), + Connector::Paypal => Ok(connector_data.paypal), + Connector::Payu => Ok(connector_data.payu), + Connector::Placetopay => Ok(connector_data.placetopay), + Connector::Plaid => Ok(connector_data.plaid), + Connector::Powertranz => Ok(connector_data.powertranz), + Connector::Rapyd => Ok(connector_data.rapyd), + Connector::Riskified => Ok(connector_data.riskified), + Connector::Shift4 => Ok(connector_data.shift4), + Connector::Signifyd => Ok(connector_data.signifyd), + Connector::Square => Ok(connector_data.square), + Connector::Stax => Ok(connector_data.stax), + Connector::Stripe => Ok(connector_data.stripe), + Connector::Trustpay => Ok(connector_data.trustpay), + Connector::Tsys => Ok(connector_data.tsys), + Connector::Volt => Ok(connector_data.volt), + Connector::Wise => Err("Use get_payout_connector_config".to_string()), + Connector::Worldline => Ok(connector_data.worldline), + Connector::Worldpay => Ok(connector_data.worldpay), + Connector::Zen => Ok(connector_data.zen), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector1 => Ok(connector_data.dummy_connector), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector2 => Ok(connector_data.dummy_connector), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector3 => Ok(connector_data.dummy_connector), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector4 => Ok(connector_data.stripe_test), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector5 => Ok(connector_data.dummy_connector), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector6 => Ok(connector_data.dummy_connector), + #[cfg(feature = "dummy_connector")] + Connector::DummyConnector7 => Ok(connector_data.paypal_test), + } + } +} diff --git a/crates/connector_configs/src/lib.rs b/crates/connector_configs/src/lib.rs new file mode 100644 index 00000000000..d480871c747 --- /dev/null +++ b/crates/connector_configs/src/lib.rs @@ -0,0 +1,4 @@ +pub mod common_config; +pub mod connector; +pub mod response_modifier; +pub mod transformer; diff --git a/crates/connector_configs/src/response_modifier.rs b/crates/connector_configs/src/response_modifier.rs new file mode 100644 index 00000000000..80332612c13 --- /dev/null +++ b/crates/connector_configs/src/response_modifier.rs @@ -0,0 +1,368 @@ +use crate::common_config::{ + CardProvider, ConnectorApiIntegrationPayload, DashboardMetaData, DashboardPaymentMethodPayload, + DashboardRequestPayload, GoogleApiModelData, GooglePayData, GpayDashboardPayLoad, Provider, +}; + +impl ConnectorApiIntegrationPayload { + pub fn get_transformed_response_payload(response: Self) -> DashboardRequestPayload { + let mut wallet_details: Vec = Vec::new(); + let mut bank_redirect_details: Vec = Vec::new(); + let mut pay_later_details: Vec = Vec::new(); + let mut debit_details: Vec = Vec::new(); + let mut credit_details: Vec = Vec::new(); + let mut bank_transfer_details: Vec = Vec::new(); + let mut crypto_details: Vec = Vec::new(); + let mut bank_debit_details: Vec = Vec::new(); + let mut reward_details: Vec = Vec::new(); + let mut upi_details: Vec = Vec::new(); + let mut voucher_details: Vec = Vec::new(); + let mut gift_card_details: Vec = Vec::new(); + let mut card_redirect_details: Vec = Vec::new(); + + if let Some(payment_methods_enabled) = response.payment_methods_enabled.clone() { + for methods in payment_methods_enabled { + match methods.payment_method { + api_models::enums::PaymentMethod::Card => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + match method_type.payment_method_type { + api_models::enums::PaymentMethodType::Credit => { + if let Some(card_networks) = method_type.card_networks { + for card in card_networks { + credit_details.push(CardProvider { + payment_method_type: card, + accepted_currencies: method_type + .accepted_currencies + .clone(), + accepted_countries: method_type + .accepted_countries + .clone(), + }) + } + } + } + api_models::enums::PaymentMethodType::Debit => { + if let Some(card_networks) = method_type.card_networks { + for card in card_networks { + // debit_details.push(card) + debit_details.push(CardProvider { + payment_method_type: card, + accepted_currencies: method_type + .accepted_currencies + .clone(), + accepted_countries: method_type + .accepted_countries + .clone(), + }) + } + } + } + _ => (), + } + } + } + } + api_models::enums::PaymentMethod::Wallet => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + // wallet_details.push(method_type.payment_method_type) + wallet_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::BankRedirect => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + bank_redirect_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::PayLater => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + pay_later_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::BankTransfer => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + bank_transfer_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::Crypto => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + crypto_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::BankDebit => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + bank_debit_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::Reward => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + reward_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::Upi => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + upi_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::Voucher => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + voucher_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::GiftCard => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + gift_card_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + api_models::enums::PaymentMethod::CardRedirect => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + card_redirect_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + }) + } + } + } + } + } + } + + let upi = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Upi, + payment_method_type: api_models::enums::PaymentMethod::Upi.to_string(), + provider: Some(upi_details), + card_provider: None, + }; + + let voucher: DashboardPaymentMethodPayload = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Voucher, + payment_method_type: api_models::enums::PaymentMethod::Voucher.to_string(), + provider: Some(voucher_details), + card_provider: None, + }; + + let gift_card: DashboardPaymentMethodPayload = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::GiftCard, + payment_method_type: api_models::enums::PaymentMethod::GiftCard.to_string(), + provider: Some(gift_card_details), + card_provider: None, + }; + + let reward = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Reward, + payment_method_type: api_models::enums::PaymentMethod::Reward.to_string(), + provider: Some(reward_details), + card_provider: None, + }; + + let wallet = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Wallet, + payment_method_type: api_models::enums::PaymentMethod::Wallet.to_string(), + provider: Some(wallet_details), + card_provider: None, + }; + let bank_redirect = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::BankRedirect, + payment_method_type: api_models::enums::PaymentMethod::BankRedirect.to_string(), + provider: Some(bank_redirect_details), + card_provider: None, + }; + + let bank_debit = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::BankDebit, + payment_method_type: api_models::enums::PaymentMethod::BankDebit.to_string(), + provider: Some(bank_debit_details), + card_provider: None, + }; + + let bank_transfer = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::BankTransfer, + payment_method_type: api_models::enums::PaymentMethod::BankTransfer.to_string(), + provider: Some(bank_transfer_details), + card_provider: None, + }; + + let crypto = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Crypto, + payment_method_type: api_models::enums::PaymentMethod::Crypto.to_string(), + provider: Some(crypto_details), + card_provider: None, + }; + + let card_redirect = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::CardRedirect, + payment_method_type: api_models::enums::PaymentMethod::CardRedirect.to_string(), + provider: Some(card_redirect_details), + card_provider: None, + }; + let pay_later = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::PayLater, + payment_method_type: api_models::enums::PaymentMethod::PayLater.to_string(), + provider: Some(pay_later_details), + card_provider: None, + }; + let debit_details = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Card, + payment_method_type: api_models::enums::PaymentMethodType::Debit.to_string(), + provider: None, + card_provider: Some(debit_details), + }; + let credit_details = DashboardPaymentMethodPayload { + payment_method: api_models::enums::PaymentMethod::Card, + payment_method_type: api_models::enums::PaymentMethodType::Credit.to_string(), + provider: None, + card_provider: Some(credit_details), + }; + + let google_pay = Self::get_google_pay_metadata_response(response.clone()); + let account_name = match response.metadata.clone() { + Some(meta_data) => meta_data.account_name, + _ => None, + }; + + let merchant_account_id = match response.metadata.clone() { + Some(meta_data) => meta_data.merchant_account_id, + _ => None, + }; + let merchant_id = match response.metadata.clone() { + Some(meta_data) => meta_data.merchant_id, + _ => None, + }; + let terminal_id = match response.metadata.clone() { + Some(meta_data) => meta_data.terminal_id, + _ => None, + }; + let apple_pay = match response.metadata.clone() { + Some(meta_data) => meta_data.apple_pay, + _ => None, + }; + let apple_pay_combined = match response.metadata.clone() { + Some(meta_data) => meta_data.apple_pay_combined, + _ => None, + }; + let merchant_config_currency = match response.metadata.clone() { + Some(meta_data) => meta_data.merchant_config_currency, + _ => None, + }; + + let meta_data = DashboardMetaData { + merchant_config_currency, + merchant_account_id, + apple_pay, + apple_pay_combined, + google_pay, + account_name, + terminal_id, + merchant_id, + }; + + DashboardRequestPayload { + connector: response.connector_name, + payment_methods_enabled: Some(vec![ + upi, + voucher, + reward, + wallet, + bank_redirect, + bank_debit, + bank_transfer, + crypto, + card_redirect, + pay_later, + debit_details, + credit_details, + gift_card, + ]), + metadata: Some(meta_data), + } + } + + pub fn get_google_pay_metadata_response(response: Self) -> Option { + match response.metadata { + Some(meta_data) => { + match meta_data.google_pay { + Some(google_pay) => match google_pay { + GoogleApiModelData::Standard(standard_data) => { + let data = standard_data.allowed_payment_methods.first().map( + |allowed_pm| { + allowed_pm.tokenization_specification.parameters.clone() + }, + )?; + Some(GooglePayData::Standard(GpayDashboardPayLoad { + gateway_merchant_id: data.gateway_merchant_id, + stripe_version: data.stripe_version, + stripe_publishable_key: data.stripe_publishable_key, + merchant_name: standard_data.merchant_info.merchant_name, + merchant_id: standard_data.merchant_info.merchant_id, + })) + } + GoogleApiModelData::Zen(data) => Some(GooglePayData::Zen(data)), + }, + None => None, + } + } + None => None, + } + } +} diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs new file mode 100644 index 00000000000..be68a0c5f94 --- /dev/null +++ b/crates/connector_configs/src/transformer.rs @@ -0,0 +1,284 @@ +use std::str::FromStr; + +use api_models::{ + enums::{ + Connector, PaymentMethod, PaymentMethodType, + PaymentMethodType::{AliPay, ApplePay, GooglePay, Klarna, Paypal, WeChatPay}, + }, + payment_methods, payments, +}; + +use crate::common_config::{ + ApiModelMetaData, ConnectorApiIntegrationPayload, DashboardMetaData, DashboardRequestPayload, + GoogleApiModelData, GooglePayData, PaymentMethodsEnabled, Provider, +}; + +impl DashboardRequestPayload { + pub fn transform_card( + payment_method_type: PaymentMethodType, + card_provider: Vec, + ) -> payment_methods::RequestPaymentMethodTypes { + payment_methods::RequestPaymentMethodTypes { + payment_method_type, + card_networks: Some(card_provider), + minimum_amount: Some(0), + maximum_amount: Some(68607706), + recurring_enabled: true, + installment_payment_enabled: false, + accepted_currencies: None, + accepted_countries: None, + payment_experience: None, + } + } + + pub fn get_payment_experience( + connector: Connector, + payment_method_type: PaymentMethodType, + payment_method: PaymentMethod, + ) -> Option { + match payment_method { + PaymentMethod::BankRedirect => None, + _ => match (connector, payment_method_type) { + #[cfg(feature = "dummy_connector")] + (Connector::DummyConnector4, _) | (Connector::DummyConnector7, _) => { + Some(api_models::enums::PaymentExperience::RedirectToUrl) + } + (Connector::Zen, GooglePay) | (Connector::Zen, ApplePay) => { + Some(api_models::enums::PaymentExperience::RedirectToUrl) + } + (Connector::Braintree, Paypal) | (Connector::Klarna, Klarna) => { + Some(api_models::enums::PaymentExperience::InvokeSdkClient) + } + (Connector::Globepay, AliPay) + | (Connector::Globepay, WeChatPay) + | (Connector::Stripe, WeChatPay) => { + Some(api_models::enums::PaymentExperience::DisplayQrCode) + } + (_, GooglePay) | (_, ApplePay) => { + Some(api_models::enums::PaymentExperience::InvokeSdkClient) + } + _ => Some(api_models::enums::PaymentExperience::RedirectToUrl), + }, + } + } + pub fn transform_payment_method( + connector: Connector, + provider: Vec, + payment_method: PaymentMethod, + ) -> Vec { + let mut payment_method_types = Vec::new(); + for method_type in provider { + let data = payment_methods::RequestPaymentMethodTypes { + payment_method_type: method_type.payment_method_type, + card_networks: None, + minimum_amount: Some(0), + maximum_amount: Some(68607706), + recurring_enabled: true, + installment_payment_enabled: false, + accepted_currencies: method_type.accepted_currencies, + accepted_countries: method_type.accepted_countries, + payment_experience: Self::get_payment_experience( + connector, + method_type.payment_method_type, + payment_method, + ), + }; + payment_method_types.push(data) + } + payment_method_types + } + + pub fn create_connector_request( + request: Self, + api_response: ConnectorApiIntegrationPayload, + ) -> ConnectorApiIntegrationPayload { + let mut card_payment_method_types = Vec::new(); + let mut payment_method_enabled = Vec::new(); + + if let Some(payment_methods_enabled) = request.payment_methods_enabled.clone() { + for payload in payment_methods_enabled { + match payload.payment_method { + api_models::enums::PaymentMethod::Card => { + if let Some(card_provider) = payload.card_provider { + let payment_type = api_models::enums::PaymentMethodType::from_str( + &payload.payment_method_type, + ) + .map_err(|_| "Invalid key received".to_string()); + + if let Ok(payment_type) = payment_type { + for method in card_provider { + let data = payment_methods::RequestPaymentMethodTypes { + payment_method_type: payment_type, + card_networks: Some(vec![method.payment_method_type]), + minimum_amount: Some(0), + maximum_amount: Some(68607706), + recurring_enabled: true, + installment_payment_enabled: false, + accepted_currencies: None, + accepted_countries: None, + payment_experience: None, + }; + card_payment_method_types.push(data) + } + } + } + } + + api_models::enums::PaymentMethod::Wallet + | api_models::enums::PaymentMethod::BankRedirect + | api_models::enums::PaymentMethod::PayLater + | api_models::enums::PaymentMethod::BankTransfer + | api_models::enums::PaymentMethod::Crypto + | api_models::enums::PaymentMethod::BankDebit + | api_models::enums::PaymentMethod::Reward + | api_models::enums::PaymentMethod::Upi + | api_models::enums::PaymentMethod::Voucher + | api_models::enums::PaymentMethod::GiftCard + | api_models::enums::PaymentMethod::CardRedirect => { + if let Some(provider) = payload.provider { + let val = Self::transform_payment_method( + request.connector, + provider, + payload.payment_method, + ); + if !val.is_empty() { + let methods = PaymentMethodsEnabled { + payment_method: payload.payment_method, + payment_method_types: Some(val), + }; + payment_method_enabled.push(methods); + } + } + } + }; + } + if !card_payment_method_types.is_empty() { + let card = PaymentMethodsEnabled { + payment_method: api_models::enums::PaymentMethod::Card, + payment_method_types: Some(card_payment_method_types), + }; + payment_method_enabled.push(card); + } + } + + let metadata = Self::transform_metedata(request); + ConnectorApiIntegrationPayload { + connector_type: api_response.connector_type, + profile_id: api_response.profile_id, + connector_name: api_response.connector_name, + connector_label: api_response.connector_label, + merchant_connector_id: api_response.merchant_connector_id, + disabled: api_response.disabled, + test_mode: api_response.test_mode, + payment_methods_enabled: Some(payment_method_enabled), + connector_webhook_details: api_response.connector_webhook_details, + metadata, + } + } + + pub fn transform_metedata(request: Self) -> Option { + let default_metadata = DashboardMetaData { + apple_pay_combined: None, + google_pay: None, + apple_pay: None, + account_name: None, + terminal_id: None, + merchant_account_id: None, + merchant_id: None, + merchant_config_currency: None, + }; + let meta_data = match request.metadata { + Some(data) => data, + None => default_metadata, + }; + let google_pay = Self::get_google_pay_details(meta_data.clone(), request.connector); + let account_name = meta_data.account_name.clone(); + let merchant_account_id = meta_data.merchant_account_id.clone(); + let merchant_id = meta_data.merchant_id.clone(); + let terminal_id = meta_data.terminal_id.clone(); + let apple_pay = meta_data.apple_pay; + let apple_pay_combined = meta_data.apple_pay_combined; + let merchant_config_currency = meta_data.merchant_config_currency; + Some(ApiModelMetaData { + google_pay, + apple_pay, + account_name, + merchant_account_id, + terminal_id, + merchant_id, + merchant_config_currency, + apple_pay_combined, + }) + } + + fn get_custom_gateway_name(connector: Connector) -> String { + match connector { + Connector::Checkout => String::from("checkoutltd"), + Connector::Nuvei => String::from("nuveidigital"), + Connector::Authorizedotnet => String::from("authorizenet"), + Connector::Globalpay => String::from("globalpayments"), + Connector::Bankofamerica | Connector::Cybersource => String::from("cybersource"), + _ => connector.to_string(), + } + } + fn get_google_pay_details( + meta_data: DashboardMetaData, + connector: Connector, + ) -> Option { + match meta_data.google_pay { + Some(gpay_data) => { + let google_pay_data = match gpay_data { + GooglePayData::Standard(data) => { + let token_parameter = payments::GpayTokenParameters { + gateway: Self::get_custom_gateway_name(connector), + gateway_merchant_id: data.gateway_merchant_id, + stripe_version: match connector { + Connector::Stripe => Some(String::from("2018-10-31")), + _ => None, + }, + stripe_publishable_key: match connector { + Connector::Stripe => data.stripe_publishable_key, + _ => None, + }, + }; + let merchant_info = payments::GpayMerchantInfo { + merchant_name: data.merchant_name, + merchant_id: data.merchant_id, + }; + let token_specification = payments::GpayTokenizationSpecification { + token_specification_type: String::from("PAYMENT_GATEWAY"), + parameters: token_parameter, + }; + let allowed_payment_methods_parameters = + payments::GpayAllowedMethodsParameters { + allowed_auth_methods: vec![ + "PAN_ONLY".to_string(), + "CRYPTOGRAM_3DS".to_string(), + ], + allowed_card_networks: vec![ + "AMEX".to_string(), + "DISCOVER".to_string(), + "INTERAC".to_string(), + "JCB".to_string(), + "MASTERCARD".to_string(), + "VISA".to_string(), + ], + }; + let allowed_payment_methods = payments::GpayAllowedPaymentMethods { + payment_method_type: String::from("CARD"), + parameters: allowed_payment_methods_parameters, + tokenization_specification: token_specification, + }; + GoogleApiModelData::Standard(payments::GpayMetaData { + merchant_info, + allowed_payment_methods: vec![allowed_payment_methods], + }) + } + GooglePayData::Zen(data) => GoogleApiModelData::Zen(data), + }; + Some(google_pay_data) + } + _ => None, + } + } +} diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml new file mode 100644 index 00000000000..dfa0a9ec923 --- /dev/null +++ b/crates/connector_configs/toml/development.toml @@ -0,0 +1,2503 @@ +[aci] +[[aci.credit]] + payment_method_type = "Mastercard" +[[aci.credit]] + payment_method_type = "Visa" +[[aci.credit]] + payment_method_type = "Interac" +[[aci.credit]] + payment_method_type = "AmericanExpress" +[[aci.credit]] + payment_method_type = "JCB" +[[aci.credit]] + payment_method_type = "DinersClub" +[[aci.credit]] + payment_method_type = "Discover" +[[aci.credit]] + payment_method_type = "CartesBancaires" +[[aci.credit]] + payment_method_type = "UnionPay" +[[aci.debit]] + payment_method_type = "Mastercard" +[[aci.debit]] + payment_method_type = "Visa" +[[aci.debit]] + payment_method_type = "Interac" +[[aci.debit]] + payment_method_type = "AmericanExpress" +[[aci.debit]] + payment_method_type = "JCB" +[[aci.debit]] + payment_method_type = "DinersClub" +[[aci.debit]] + payment_method_type = "Discover" +[[aci.debit]] + payment_method_type = "CartesBancaires" +[[aci.debit]] + payment_method_type = "UnionPay" +[[aci.wallet]] + payment_method_type = "ali_pay" +[[aci.wallet]] + payment_method_type = "mb_way" +[[aci.bank_redirect]] + payment_method_type = "ideal" +[[aci.bank_redirect]] + payment_method_type = "giropay" +[[aci.bank_redirect]] + payment_method_type = "sofort" +[[aci.bank_redirect]] + payment_method_type = "eps" +[[aci.bank_redirect]] + payment_method_type = "przelewy24" +[[aci.bank_redirect]] + payment_method_type = "trustly" +[[aci.bank_redirect]] + payment_method_type = "interac" +[aci.connector_auth.BodyKey] +api_key="API Key" +key1="Entity ID" +[aci.connector_webhook_details] +merchant_secret="Source verification key" + + +[adyen] +[[adyen.credit]] + payment_method_type = "Mastercard" +[[adyen.credit]] + payment_method_type = "Visa" +[[adyen.credit]] + payment_method_type = "Interac" +[[adyen.credit]] + payment_method_type = "AmericanExpress" +[[adyen.credit]] + payment_method_type = "JCB" +[[adyen.credit]] + payment_method_type = "DinersClub" +[[adyen.credit]] + payment_method_type = "Discover" +[[adyen.credit]] + payment_method_type = "CartesBancaires" +[[adyen.credit]] + payment_method_type = "UnionPay" +[[adyen.debit]] + payment_method_type = "Mastercard" +[[adyen.debit]] + payment_method_type = "Visa" +[[adyen.debit]] + payment_method_type = "Interac" +[[adyen.debit]] + payment_method_type = "AmericanExpress" +[[adyen.debit]] + payment_method_type = "JCB" +[[adyen.debit]] + payment_method_type = "DinersClub" +[[adyen.debit]] + payment_method_type = "Discover" +[[adyen.debit]] + payment_method_type = "CartesBancaires" +[[adyen.debit]] + payment_method_type = "UnionPay" +[[adyen.pay_later]] + payment_method_type = "klarna" +[[adyen.pay_later]] + payment_method_type = "affirm" +[[adyen.pay_later]] + payment_method_type = "afterpay_clearpay" +[[adyen.pay_later]] + payment_method_type = "pay_bright" +[[adyen.pay_later]] + payment_method_type = "walley" +[[adyen.pay_later]] + payment_method_type = "alma" +[[adyen.pay_later]] + payment_method_type = "atome" +[[adyen.bank_debit]] + payment_method_type = "ach" +[[adyen.bank_debit]] + payment_method_type = "bacs" +[[adyen.bank_debit]] + payment_method_type = "sepa" +[[adyen.bank_redirect]] + payment_method_type = "ideal" +[[adyen.bank_redirect]] + payment_method_type = "giropay" +[[adyen.bank_redirect]] + payment_method_type = "sofort" +[[adyen.bank_redirect]] + payment_method_type = "eps" +[[adyen.bank_redirect]] + payment_method_type = "blik" +[[adyen.bank_redirect]] + payment_method_type = "przelewy24" +[[adyen.bank_redirect]] + payment_method_type = "trustly" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_czech_republic" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_finland" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_poland" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_slovakia" +[[adyen.bank_redirect]] + payment_method_type = "bancontact_card" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_fpx" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_thailand" +[[adyen.bank_redirect]] + payment_method_type = "bizum" +[[adyen.bank_redirect]] + payment_method_type = "open_banking_uk" +[[adyen.bank_transfer]] + payment_method_type = "permata_bank_transfer" +[[adyen.bank_transfer]] + payment_method_type = "bca_bank_transfer" +[[adyen.bank_transfer]] + payment_method_type = "bni_va" +[[adyen.bank_transfer]] + payment_method_type = "bri_va" +[[adyen.bank_transfer]] + payment_method_type = "cimb_va" +[[adyen.bank_transfer]] + payment_method_type = "danamon_va" +[[adyen.bank_transfer]] + payment_method_type = "mandiri_va" +[[adyen.wallet]] + payment_method_type = "apple_pay" +[[adyen.wallet]] + payment_method_type = "google_pay" +[[adyen.wallet]] + payment_method_type = "paypal" +[[adyen.wallet]] + payment_method_type = "we_chat_pay" +[[adyen.wallet]] + payment_method_type = "ali_pay" +[[adyen.wallet]] + payment_method_type = "mb_way" +[[adyen.wallet]] + payment_method_type = "ali_pay_hk" +[[adyen.wallet]] + payment_method_type = "go_pay" +[[adyen.wallet]] + payment_method_type = "kakao_pay" +[[adyen.wallet]] + payment_method_type = "twint" +[[adyen.wallet]] + payment_method_type = "gcash" +[[adyen.wallet]] + payment_method_type = "vipps" +[[adyen.wallet]] + payment_method_type = "dana" +[[adyen.wallet]] + payment_method_type = "momo" +[[adyen.wallet]] + payment_method_type = "swish" +[[adyen.wallet]] + payment_method_type = "touch_n_go" +[[adyen.voucher]] + payment_method_type = "boleto" +[[adyen.voucher]] + payment_method_type = "alfamart" +[[adyen.voucher]] + payment_method_type = "indomaret" +[[adyen.voucher]] + payment_method_type = "oxxo" +[[adyen.voucher]] + payment_method_type = "seven_eleven" +[[adyen.voucher]] + payment_method_type = "lawson" +[[adyen.voucher]] + payment_method_type = "mini_stop" +[[adyen.voucher]] + payment_method_type = "family_mart" +[[adyen.voucher]] + payment_method_type = "seicomart" +[[adyen.voucher]] + payment_method_type = "pay_easy" +[[adyen.gift_card]] + payment_method_type = "pay_safe_card" +[[adyen.gift_card]] + payment_method_type = "givex" +[[adyen.card_redirect]] + payment_method_type = "benefit" +[[adyen.card_redirect]] + payment_method_type = "knet" +[[adyen.card_redirect]] + payment_method_type = "momo_atm" +[adyen.connector_auth.BodyKey] +api_key="Adyen API Key" +key1="Adyen Account Id" +[adyen.connector_webhook_details] +merchant_secret="Source verification key" +[adyen.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[adyen.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[adyen.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[airwallex] +[[airwallex.credit]] + payment_method_type = "Mastercard" +[[airwallex.credit]] + payment_method_type = "Visa" +[[airwallex.credit]] + payment_method_type = "Interac" +[[airwallex.credit]] + payment_method_type = "AmericanExpress" +[[airwallex.credit]] + payment_method_type = "JCB" +[[airwallex.credit]] + payment_method_type = "DinersClub" +[[airwallex.credit]] + payment_method_type = "Discover" +[[airwallex.credit]] + payment_method_type = "CartesBancaires" +[[airwallex.credit]] + payment_method_type = "UnionPay" +[[airwallex.debit]] + payment_method_type = "Mastercard" +[[airwallex.debit]] + payment_method_type = "Visa" +[[airwallex.debit]] + payment_method_type = "Interac" +[[airwallex.debit]] + payment_method_type = "AmericanExpress" +[[airwallex.debit]] + payment_method_type = "JCB" +[[airwallex.debit]] + payment_method_type = "DinersClub" +[[airwallex.debit]] + payment_method_type = "Discover" +[[airwallex.debit]] + payment_method_type = "CartesBancaires" +[[airwallex.debit]] + payment_method_type = "UnionPay" +[[airwallex.wallet]] + payment_method_type = "google_pay" +[airwallex.connector_auth.BodyKey] +api_key="API Key" +key1="Client ID" +[airwallex.connector_webhook_details] +merchant_secret="Source verification key" + + +[authorizedotnet] +[[authorizedotnet.credit]] + payment_method_type = "Mastercard" +[[authorizedotnet.credit]] + payment_method_type = "Visa" +[[authorizedotnet.credit]] + payment_method_type = "Interac" +[[authorizedotnet.credit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.credit]] + payment_method_type = "JCB" +[[authorizedotnet.credit]] + payment_method_type = "DinersClub" +[[authorizedotnet.credit]] + payment_method_type = "Discover" +[[authorizedotnet.credit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.credit]] + payment_method_type = "UnionPay" +[[authorizedotnet.debit]] + payment_method_type = "Mastercard" +[[authorizedotnet.debit]] + payment_method_type = "Visa" +[[authorizedotnet.debit]] + payment_method_type = "Interac" +[[authorizedotnet.debit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.debit]] + payment_method_type = "JCB" +[[authorizedotnet.debit]] + payment_method_type = "DinersClub" +[[authorizedotnet.debit]] + payment_method_type = "Discover" +[[authorizedotnet.debit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.debit]] + payment_method_type = "UnionPay" +[[authorizedotnet.wallet]] + payment_method_type = "google_pay" +[[authorizedotnet.wallet]] + payment_method_type = "paypal" +[authorizedotnet.connector_auth.BodyKey] +api_key="API Login ID" +key1="Transaction Key" +[authorizedotnet.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" +[authorizedotnet.connector_webhook_details] +merchant_secret="Source verification key" + +[bambora] +[[bambora.credit]] + payment_method_type = "Mastercard" +[[bambora.credit]] + payment_method_type = "Visa" +[[bambora.credit]] + payment_method_type = "Interac" +[[bambora.credit]] + payment_method_type = "AmericanExpress" +[[bambora.credit]] + payment_method_type = "JCB" +[[bambora.credit]] + payment_method_type = "DinersClub" +[[bambora.credit]] + payment_method_type = "Discover" +[[bambora.credit]] + payment_method_type = "CartesBancaires" +[[bambora.credit]] + payment_method_type = "UnionPay" +[[bambora.debit]] + payment_method_type = "Mastercard" +[[bambora.debit]] + payment_method_type = "Visa" +[[bambora.debit]] + payment_method_type = "Interac" +[[bambora.debit]] + payment_method_type = "AmericanExpress" +[[bambora.debit]] + payment_method_type = "JCB" +[[bambora.debit]] + payment_method_type = "DinersClub" +[[bambora.debit]] + payment_method_type = "Discover" +[[bambora.debit]] + payment_method_type = "CartesBancaires" +[[bambora.debit]] + payment_method_type = "UnionPay" +[[bambora.wallet]] + payment_method_type = "apple_pay" +[[bambora.wallet]] + payment_method_type = "paypal" +[bambora.connector_auth.BodyKey] +api_key="Passcode" +key1="Merchant Id" +[bambora.connector_webhook_details] +merchant_secret="Source verification key" +[bambora.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bambora.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica] +[[bankofamerica.credit]] + payment_method_type = "Mastercard" +[[bankofamerica.credit]] + payment_method_type = "Visa" +[[bankofamerica.credit]] + payment_method_type = "Interac" +[[bankofamerica.credit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.credit]] + payment_method_type = "JCB" +[[bankofamerica.credit]] + payment_method_type = "DinersClub" +[[bankofamerica.credit]] + payment_method_type = "Discover" +[[bankofamerica.credit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.credit]] + payment_method_type = "UnionPay" +[[bankofamerica.debit]] + payment_method_type = "Mastercard" +[[bankofamerica.debit]] + payment_method_type = "Visa" +[[bankofamerica.debit]] + payment_method_type = "Interac" +[[bankofamerica.debit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.debit]] + payment_method_type = "JCB" +[[bankofamerica.debit]] + payment_method_type = "DinersClub" +[[bankofamerica.debit]] + payment_method_type = "Discover" +[[bankofamerica.debit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.debit]] + payment_method_type = "UnionPay" +[[bankofamerica.wallet]] + payment_method_type = "apple_pay" +[[bankofamerica.wallet]] + payment_method_type = "google_pay" +[bankofamerica.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[bankofamerica.connector_webhook_details] +merchant_secret="Source verification key" + +[bankofamerica.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bankofamerica.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[bitpay] +[[bitpay.crypto]] + payment_method_type = "crypto_currency" +[bitpay.connector_auth.HeaderKey] +api_key="API Key" +[bitpay.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap] +[[bluesnap.credit]] + payment_method_type = "Mastercard" +[[bluesnap.credit]] + payment_method_type = "Visa" +[[bluesnap.credit]] + payment_method_type = "Interac" +[[bluesnap.credit]] + payment_method_type = "AmericanExpress" +[[bluesnap.credit]] + payment_method_type = "JCB" +[[bluesnap.credit]] + payment_method_type = "DinersClub" +[[bluesnap.credit]] + payment_method_type = "Discover" +[[bluesnap.credit]] + payment_method_type = "CartesBancaires" +[[bluesnap.credit]] + payment_method_type = "UnionPay" +[[bluesnap.debit]] + payment_method_type = "Mastercard" +[[bluesnap.debit]] + payment_method_type = "Visa" +[[bluesnap.debit]] + payment_method_type = "Interac" +[[bluesnap.debit]] + payment_method_type = "AmericanExpress" +[[bluesnap.debit]] + payment_method_type = "JCB" +[[bluesnap.debit]] + payment_method_type = "DinersClub" +[[bluesnap.debit]] + payment_method_type = "Discover" +[[bluesnap.debit]] + payment_method_type = "CartesBancaires" +[[bluesnap.debit]] + payment_method_type = "UnionPay" +[[bluesnap.wallet]] + payment_method_type = "google_pay" +[[bluesnap.wallet]] + payment_method_type = "apple_pay" +[bluesnap.connector_auth.BodyKey] +api_key="Password" +key1="Username" +[bluesnap.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[bluesnap.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bluesnap.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" +[bluesnap.metadata] +merchant_id="Merchant Id" + +[boku] +[[boku.wallet]] + payment_method_type = "dana" +[[boku.wallet]] + payment_method_type = "gcash" +[[boku.wallet]] + payment_method_type = "go_pay" +[[boku.wallet]] + payment_method_type = "kakao_pay" +[[boku.wallet]] + payment_method_type = "momo" +[boku.connector_auth.BodyKey] +api_key="API KEY" +key1= "MERCHANT ID" +[boku.connector_webhook_details] +merchant_secret="Source verification key" + + +[braintree] +[[braintree.credit]] + payment_method_type = "Mastercard" +[[braintree.credit]] + payment_method_type = "Visa" +[[braintree.credit]] + payment_method_type = "Interac" +[[braintree.credit]] + payment_method_type = "AmericanExpress" +[[braintree.credit]] + payment_method_type = "JCB" +[[braintree.credit]] + payment_method_type = "DinersClub" +[[braintree.credit]] + payment_method_type = "Discover" +[[braintree.credit]] + payment_method_type = "CartesBancaires" +[[braintree.credit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "Mastercard" +[[braintree.debit]] + payment_method_type = "Visa" +[[braintree.debit]] + payment_method_type = "Interac" +[[braintree.debit]] + payment_method_type = "AmericanExpress" +[[braintree.debit]] + payment_method_type = "JCB" +[[braintree.debit]] + payment_method_type = "DinersClub" +[[braintree.debit]] + payment_method_type = "Discover" +[[braintree.debit]] + payment_method_type = "CartesBancaires" +[[braintree.debit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "UnionPay" +[braintree.connector_webhook_details] +merchant_secret="Source verification key" + + +[braintree.connector_auth.SignatureKey] +api_key="Public Key" +key1="Merchant Id" +api_secret="Private Key" +[braintree.metadata] +merchant_account_id="Merchant Account Id" +merchant_config_currency="Currency" + +[cashtocode] +[[cashtocode.reward]] + payment_method_type = "classic" +[[cashtocode.reward]] + payment_method_type = "evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout] +[[checkout.credit]] + payment_method_type = "Mastercard" +[[checkout.credit]] + payment_method_type = "Visa" +[[checkout.credit]] + payment_method_type = "Interac" +[[checkout.credit]] + payment_method_type = "AmericanExpress" +[[checkout.credit]] + payment_method_type = "JCB" +[[checkout.credit]] + payment_method_type = "DinersClub" +[[checkout.credit]] + payment_method_type = "Discover" +[[checkout.credit]] + payment_method_type = "CartesBancaires" +[[checkout.credit]] + payment_method_type = "UnionPay" +[[checkout.debit]] + payment_method_type = "Mastercard" +[[checkout.debit]] + payment_method_type = "Visa" +[[checkout.debit]] + payment_method_type = "Interac" +[[checkout.debit]] + payment_method_type = "AmericanExpress" +[[checkout.debit]] + payment_method_type = "JCB" +[[checkout.debit]] + payment_method_type = "DinersClub" +[[checkout.debit]] + payment_method_type = "Discover" +[[checkout.debit]] + payment_method_type = "CartesBancaires" +[[checkout.debit]] + payment_method_type = "UnionPay" +[[checkout.wallet]] + payment_method_type = "apple_pay" +[[checkout.wallet]] + payment_method_type = "google_pay" +[[checkout.wallet]] + payment_method_type = "paypal" +[checkout.connector_auth.SignatureKey] +api_key="Checkout API Public Key" +key1="Processing Channel ID" +api_secret="Checkout API Secret Key" +[checkout.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[checkout.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[checkout.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[coinbase] +[[coinbase.crypto]] + payment_method_type = "crypto_currency" +[coinbase.connector_auth.HeaderKey] +api_key="API Key" +[coinbase.connector_webhook_details] +merchant_secret="Source verification key" + +[cryptopay] +[[cryptopay.crypto]] + payment_method_type = "crypto_currency" +[cryptopay.connector_auth.BodyKey] +api_key="API Key" +key1="Secret Key" +[cryptopay.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource] +[[cybersource.credit]] + payment_method_type = "Mastercard" +[[cybersource.credit]] + payment_method_type = "Visa" +[[cybersource.credit]] + payment_method_type = "Interac" +[[cybersource.credit]] + payment_method_type = "AmericanExpress" +[[cybersource.credit]] + payment_method_type = "JCB" +[[cybersource.credit]] + payment_method_type = "DinersClub" +[[cybersource.credit]] + payment_method_type = "Discover" +[[cybersource.credit]] + payment_method_type = "CartesBancaires" +[[cybersource.credit]] + payment_method_type = "UnionPay" +[[cybersource.debit]] + payment_method_type = "Mastercard" +[[cybersource.debit]] + payment_method_type = "Visa" +[[cybersource.debit]] + payment_method_type = "Interac" +[[cybersource.debit]] + payment_method_type = "AmericanExpress" +[[cybersource.debit]] + payment_method_type = "JCB" +[[cybersource.debit]] + payment_method_type = "DinersClub" +[[cybersource.debit]] + payment_method_type = "Discover" +[[cybersource.debit]] + payment_method_type = "CartesBancaires" +[[cybersource.debit]] + payment_method_type = "UnionPay" +[[cybersource.wallet]] + payment_method_type = "apple_pay" +[[cybersource.wallet]] + payment_method_type = "google_pay" +[cybersource.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[cybersource.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[cybersource.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[cybersource.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[dlocal] +[[dlocal.credit]] + payment_method_type = "Mastercard" +[[dlocal.credit]] + payment_method_type = "Visa" +[[dlocal.credit]] + payment_method_type = "Interac" +[[dlocal.credit]] + payment_method_type = "AmericanExpress" +[[dlocal.credit]] + payment_method_type = "JCB" +[[dlocal.credit]] + payment_method_type = "DinersClub" +[[dlocal.credit]] + payment_method_type = "Discover" +[[dlocal.credit]] + payment_method_type = "CartesBancaires" +[[dlocal.credit]] + payment_method_type = "UnionPay" +[[dlocal.debit]] + payment_method_type = "Mastercard" +[[dlocal.debit]] + payment_method_type = "Visa" +[[dlocal.debit]] + payment_method_type = "Interac" +[[dlocal.debit]] + payment_method_type = "AmericanExpress" +[[dlocal.debit]] + payment_method_type = "JCB" +[[dlocal.debit]] + payment_method_type = "DinersClub" +[[dlocal.debit]] + payment_method_type = "Discover" +[[dlocal.debit]] + payment_method_type = "CartesBancaires" +[[dlocal.debit]] + payment_method_type = "UnionPay" +[dlocal.connector_auth.SignatureKey] +api_key="X Login" +key1="X Trans Key" +api_secret="Secret Key" +[dlocal.connector_webhook_details] +merchant_secret="Source verification key" + +[fiserv] +[[fiserv.credit]] + payment_method_type = "Mastercard" +[[fiserv.credit]] + payment_method_type = "Visa" +[[fiserv.credit]] + payment_method_type = "Interac" +[[fiserv.credit]] + payment_method_type = "AmericanExpress" +[[fiserv.credit]] + payment_method_type = "JCB" +[[fiserv.credit]] + payment_method_type = "DinersClub" +[[fiserv.credit]] + payment_method_type = "Discover" +[[fiserv.credit]] + payment_method_type = "CartesBancaires" +[[fiserv.credit]] + payment_method_type = "UnionPay" +[[fiserv.debit]] + payment_method_type = "Mastercard" +[[fiserv.debit]] + payment_method_type = "Visa" +[[fiserv.debit]] + payment_method_type = "Interac" +[[fiserv.debit]] + payment_method_type = "AmericanExpress" +[[fiserv.debit]] + payment_method_type = "JCB" +[[fiserv.debit]] + payment_method_type = "DinersClub" +[[fiserv.debit]] + payment_method_type = "Discover" +[[fiserv.debit]] + payment_method_type = "CartesBancaires" +[[fiserv.debit]] + payment_method_type = "UnionPay" +[fiserv.connector_auth.SignatureKey] +api_key="API Key" +key1="Merchant ID" +api_secret="API Secret" +[fiserv.metadata] +terminal_id="Terminal ID" +[fiserv.connector_webhook_details] +merchant_secret="Source verification key" + +[forte] +[[forte.credit]] + payment_method_type = "Mastercard" +[[forte.credit]] + payment_method_type = "Visa" +[[forte.credit]] + payment_method_type = "Interac" +[[forte.credit]] + payment_method_type = "AmericanExpress" +[[forte.credit]] + payment_method_type = "JCB" +[[forte.credit]] + payment_method_type = "DinersClub" +[[forte.credit]] + payment_method_type = "Discover" +[[forte.credit]] + payment_method_type = "CartesBancaires" +[[forte.credit]] + payment_method_type = "UnionPay" +[[forte.debit]] + payment_method_type = "Mastercard" +[[forte.debit]] + payment_method_type = "Visa" +[[forte.debit]] + payment_method_type = "Interac" +[[forte.debit]] + payment_method_type = "AmericanExpress" +[[forte.debit]] + payment_method_type = "JCB" +[[forte.debit]] + payment_method_type = "DinersClub" +[[forte.debit]] + payment_method_type = "Discover" +[[forte.debit]] + payment_method_type = "CartesBancaires" +[[forte.debit]] + payment_method_type = "UnionPay" +[forte.connector_auth.MultiAuthKey] +api_key="API Access ID" +key1="Organization ID" +api_secret="API Secure Key" +key2="Location ID" +[forte.connector_webhook_details] +merchant_secret="Source verification key" + +[globalpay] +[[globalpay.credit]] + payment_method_type = "Mastercard" +[[globalpay.credit]] + payment_method_type = "Visa" +[[globalpay.credit]] + payment_method_type = "Interac" +[[globalpay.credit]] + payment_method_type = "AmericanExpress" +[[globalpay.credit]] + payment_method_type = "JCB" +[[globalpay.credit]] + payment_method_type = "DinersClub" +[[globalpay.credit]] + payment_method_type = "Discover" +[[globalpay.credit]] + payment_method_type = "CartesBancaires" +[[globalpay.credit]] + payment_method_type = "UnionPay" +[[globalpay.debit]] + payment_method_type = "Mastercard" +[[globalpay.debit]] + payment_method_type = "Visa" +[[globalpay.debit]] + payment_method_type = "Interac" +[[globalpay.debit]] + payment_method_type = "AmericanExpress" +[[globalpay.debit]] + payment_method_type = "JCB" +[[globalpay.debit]] + payment_method_type = "DinersClub" +[[globalpay.debit]] + payment_method_type = "Discover" +[[globalpay.debit]] + payment_method_type = "CartesBancaires" +[[globalpay.debit]] + payment_method_type = "UnionPay" +[[globalpay.bank_redirect]] + payment_method_type = "ideal" +[[globalpay.bank_redirect]] + payment_method_type = "giropay" +[[globalpay.bank_redirect]] + payment_method_type = "sofort" +[[globalpay.bank_redirect]] + payment_method_type = "eps" +[[globalpay.wallet]] + payment_method_type = "google_pay" +[[globalpay.wallet]] + payment_method_type = "paypal" +[globalpay.connector_auth.BodyKey] +api_key="Global App Key" +key1="Global App ID" +[globalpay.metadata] +account_name="Account Name" +[globalpay.connector_webhook_details] +merchant_secret="Source verification key" +[globalpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[globepay] +[[globepay.wallet]] + payment_method_type = "we_chat_pay" +[[globepay.wallet]] + payment_method_type = "ali_pay" +[globepay.connector_auth.BodyKey] +api_key="Partner Code" +key1="Credential Code" +[globepay.connector_webhook_details] +merchant_secret="Source verification key" + +[gocardless] +[[gocardless.bank_debit]] + payment_method_type = "ach" +[[gocardless.bank_debit]] + payment_method_type = "becs" +[[gocardless.bank_debit]] + payment_method_type = "sepa" +[gocardless.connector_auth.HeaderKey] +api_key="Access Token" +[gocardless.connector_webhook_details] +merchant_secret="Source verification key" + +[iatapay] +[[iatapay.upi]] + payment_method_type = "upi_collect" +[iatapay.connector_auth.SignatureKey] +api_key="Client ID" +key1="Airline ID" +api_secret="Client Secret" +[iatapay.connector_webhook_details] +merchant_secret="Source verification key" + +[klarna] +[[klarna.pay_later]] + payment_method_type = "klarna" +[klarna.connector_auth.HeaderKey] +api_key="Klarna API Key" +[klarna.connector_webhook_details] +merchant_secret="Source verification key" + +[mollie] +[[mollie.credit]] + payment_method_type = "Mastercard" +[[mollie.credit]] + payment_method_type = "Visa" +[[mollie.credit]] + payment_method_type = "Interac" +[[mollie.credit]] + payment_method_type = "AmericanExpress" +[[mollie.credit]] + payment_method_type = "JCB" +[[mollie.credit]] + payment_method_type = "DinersClub" +[[mollie.credit]] + payment_method_type = "Discover" +[[mollie.credit]] + payment_method_type = "CartesBancaires" +[[mollie.credit]] + payment_method_type = "UnionPay" +[[mollie.debit]] + payment_method_type = "Mastercard" +[[mollie.debit]] + payment_method_type = "Visa" +[[mollie.debit]] + payment_method_type = "Interac" +[[mollie.debit]] + payment_method_type = "AmericanExpress" +[[mollie.debit]] + payment_method_type = "JCB" +[[mollie.debit]] + payment_method_type = "DinersClub" +[[mollie.debit]] + payment_method_type = "Discover" +[[mollie.debit]] + payment_method_type = "CartesBancaires" +[[mollie.debit]] + payment_method_type = "UnionPay" +[[mollie.bank_redirect]] + payment_method_type = "ideal" +[[mollie.bank_redirect]] + payment_method_type = "giropay" +[[mollie.bank_redirect]] + payment_method_type = "sofort" +[[mollie.bank_redirect]] + payment_method_type = "eps" +[[mollie.bank_redirect]] + payment_method_type = "przelewy24" +[[mollie.bank_redirect]] + payment_method_type = "bancontact_card" +[[mollie.wallet]] + payment_method_type = "paypal" +[mollie.connector_auth.BodyKey] +api_key="API Key" +key1="Profile Token" +[mollie.connector_webhook_details] +merchant_secret="Source verification key" + +[multisafepay] +[[multisafepay.credit]] + payment_method_type = "Mastercard" +[[multisafepay.credit]] + payment_method_type = "Visa" +[[multisafepay.credit]] + payment_method_type = "Interac" +[[multisafepay.credit]] + payment_method_type = "AmericanExpress" +[[multisafepay.credit]] + payment_method_type = "JCB" +[[multisafepay.credit]] + payment_method_type = "DinersClub" +[[multisafepay.credit]] + payment_method_type = "Discover" +[[multisafepay.credit]] + payment_method_type = "CartesBancaires" +[[multisafepay.credit]] + payment_method_type = "UnionPay" +[[multisafepay.debit]] + payment_method_type = "Mastercard" +[[multisafepay.debit]] + payment_method_type = "Visa" +[[multisafepay.debit]] + payment_method_type = "Interac" +[[multisafepay.debit]] + payment_method_type = "AmericanExpress" +[[multisafepay.debit]] + payment_method_type = "JCB" +[[multisafepay.debit]] + payment_method_type = "DinersClub" +[[multisafepay.debit]] + payment_method_type = "Discover" +[[multisafepay.debit]] + payment_method_type = "CartesBancaires" +[[multisafepay.debit]] + payment_method_type = "UnionPay" +[[multisafepay.wallet]] + payment_method_type = "google_pay" +[[multisafepay.wallet]] + payment_method_type = "paypal" +[multisafepay.connector_auth.HeaderKey] +api_key="Enter API Key" +[multisafepay.connector_webhook_details] +merchant_secret="Source verification key" +[multisafepay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nexinets] +[[nexinets.credit]] + payment_method_type = "Mastercard" +[[nexinets.credit]] + payment_method_type = "Visa" +[[nexinets.credit]] + payment_method_type = "Interac" +[[nexinets.credit]] + payment_method_type = "AmericanExpress" +[[nexinets.credit]] + payment_method_type = "JCB" +[[nexinets.credit]] + payment_method_type = "DinersClub" +[[nexinets.credit]] + payment_method_type = "Discover" +[[nexinets.credit]] + payment_method_type = "CartesBancaires" +[[nexinets.credit]] + payment_method_type = "UnionPay" +[[nexinets.debit]] + payment_method_type = "Mastercard" +[[nexinets.debit]] + payment_method_type = "Visa" +[[nexinets.debit]] + payment_method_type = "Interac" +[[nexinets.debit]] + payment_method_type = "AmericanExpress" +[[nexinets.debit]] + payment_method_type = "JCB" +[[nexinets.debit]] + payment_method_type = "DinersClub" +[[nexinets.debit]] + payment_method_type = "Discover" +[[nexinets.debit]] + payment_method_type = "CartesBancaires" +[[nexinets.debit]] + payment_method_type = "UnionPay" +[[nexinets.bank_redirect]] + payment_method_type = "ideal" +[[nexinets.bank_redirect]] + payment_method_type = "giropay" +[[nexinets.bank_redirect]] + payment_method_type = "sofort" +[[nexinets.bank_redirect]] + payment_method_type = "eps" +[[nexinets.wallet]] + payment_method_type = "apple_pay" +[[nexinets.wallet]] + payment_method_type = "paypal" +[nexinets.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant ID" +[nexinets.connector_webhook_details] +merchant_secret="Source verification key" +[nexinets.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nexinets.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[nmi] +[[nmi.credit]] + payment_method_type = "Mastercard" +[[nmi.credit]] + payment_method_type = "Visa" +[[nmi.credit]] + payment_method_type = "Interac" +[[nmi.credit]] + payment_method_type = "AmericanExpress" +[[nmi.credit]] + payment_method_type = "JCB" +[[nmi.credit]] + payment_method_type = "DinersClub" +[[nmi.credit]] + payment_method_type = "Discover" +[[nmi.credit]] + payment_method_type = "CartesBancaires" +[[nmi.credit]] + payment_method_type = "UnionPay" +[[nmi.debit]] + payment_method_type = "Mastercard" +[[nmi.debit]] + payment_method_type = "Visa" +[[nmi.debit]] + payment_method_type = "Interac" +[[nmi.debit]] + payment_method_type = "AmericanExpress" +[[nmi.debit]] + payment_method_type = "JCB" +[[nmi.debit]] + payment_method_type = "DinersClub" +[[nmi.debit]] + payment_method_type = "Discover" +[[nmi.debit]] + payment_method_type = "CartesBancaires" +[[nmi.debit]] + payment_method_type = "UnionPay" +[[nmi.bank_redirect]] + payment_method_type = "ideal" +[[nmi.wallet]] + payment_method_type = "apple_pay" +[[nmi.wallet]] + payment_method_type = "google_pay" +[nmi.connector_auth.BodyKey] +api_key="API Key" +key1="Public Key" +[nmi.connector_webhook_details] +merchant_secret="Source verification key" + +[nmi.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nmi.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nmi.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[noon] +[[noon.credit]] + payment_method_type = "Mastercard" +[[noon.credit]] + payment_method_type = "Visa" +[[noon.credit]] + payment_method_type = "Interac" +[[noon.credit]] + payment_method_type = "AmericanExpress" +[[noon.credit]] + payment_method_type = "JCB" +[[noon.credit]] + payment_method_type = "DinersClub" +[[noon.credit]] + payment_method_type = "Discover" +[[noon.credit]] + payment_method_type = "CartesBancaires" +[[noon.credit]] + payment_method_type = "UnionPay" +[[noon.debit]] + payment_method_type = "Mastercard" +[[noon.debit]] + payment_method_type = "Visa" +[[noon.debit]] + payment_method_type = "Interac" +[[noon.debit]] + payment_method_type = "AmericanExpress" +[[noon.debit]] + payment_method_type = "JCB" +[[noon.debit]] + payment_method_type = "DinersClub" +[[noon.debit]] + payment_method_type = "Discover" +[[noon.debit]] + payment_method_type = "CartesBancaires" +[[noon.debit]] + payment_method_type = "UnionPay" +[[noon.wallet]] + payment_method_type = "apple_pay" +[[noon.wallet]] + payment_method_type = "google_pay" +[[noon.wallet]] + payment_method_type = "paypal" +[noon.connector_auth.SignatureKey] +api_key="API Key" +key1="Business Identifier" +api_secret="Application Identifier" +[noon.connector_webhook_details] +merchant_secret="Source verification key" + +[noon.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[noon.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[noon.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[nuvei] +[[nuvei.credit]] + payment_method_type = "Mastercard" +[[nuvei.credit]] + payment_method_type = "Visa" +[[nuvei.credit]] + payment_method_type = "Interac" +[[nuvei.credit]] + payment_method_type = "AmericanExpress" +[[nuvei.credit]] + payment_method_type = "JCB" +[[nuvei.credit]] + payment_method_type = "DinersClub" +[[nuvei.credit]] + payment_method_type = "Discover" +[[nuvei.credit]] + payment_method_type = "CartesBancaires" +[[nuvei.credit]] + payment_method_type = "UnionPay" +[[nuvei.debit]] + payment_method_type = "Mastercard" +[[nuvei.debit]] + payment_method_type = "Visa" +[[nuvei.debit]] + payment_method_type = "Interac" +[[nuvei.debit]] + payment_method_type = "AmericanExpress" +[[nuvei.debit]] + payment_method_type = "JCB" +[[nuvei.debit]] + payment_method_type = "DinersClub" +[[nuvei.debit]] + payment_method_type = "Discover" +[[nuvei.debit]] + payment_method_type = "CartesBancaires" +[[nuvei.debit]] + payment_method_type = "UnionPay" +[[nuvei.pay_later]] + payment_method_type = "klarna" +[[nuvei.pay_later]] + payment_method_type = "afterpay_clearpay" +[[nuvei.bank_redirect]] + payment_method_type = "ideal" +[[nuvei.bank_redirect]] + payment_method_type = "giropay" +[[nuvei.bank_redirect]] + payment_method_type = "sofort" +[[nuvei.bank_redirect]] + payment_method_type = "eps" +[[nuvei.wallet]] + payment_method_type = "apple_pay" +[[nuvei.wallet]] + payment_method_type = "google_pay" +[[nuvei.wallet]] + payment_method_type = "paypal" +[nuvei.connector_auth.SignatureKey] +api_key="Merchant ID" +key1="Merchant Site ID" +api_secret="Merchant Secret" +[nuvei.connector_webhook_details] +merchant_secret="Source verification key" + +[nuvei.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nuvei.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nuvei.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + + +[opennode] +[[opennode.crypto]] + payment_method_type = "crypto_currency" +[opennode.connector_auth.HeaderKey] +api_key="API Key" +[opennode.connector_webhook_details] +merchant_secret="Source verification key" + +[prophetpay] +[[prophetpay.card_redirect]] + payment_method_type = "card_redirect" +[prophetpay.connector_auth.SignatureKey] +api_key="Username" +key1="Token" +api_secret="Profile" + +[payme] +[[payme.credit]] + payment_method_type = "Mastercard" +[[payme.credit]] + payment_method_type = "Visa" +[[payme.credit]] + payment_method_type = "Interac" +[[payme.credit]] + payment_method_type = "AmericanExpress" +[[payme.credit]] + payment_method_type = "JCB" +[[payme.credit]] + payment_method_type = "DinersClub" +[[payme.credit]] + payment_method_type = "Discover" +[[payme.credit]] + payment_method_type = "CartesBancaires" +[[payme.credit]] + payment_method_type = "UnionPay" +[[payme.debit]] + payment_method_type = "Mastercard" +[[payme.debit]] + payment_method_type = "Visa" +[[payme.debit]] + payment_method_type = "Interac" +[[payme.debit]] + payment_method_type = "AmericanExpress" +[[payme.debit]] + payment_method_type = "JCB" +[[payme.debit]] + payment_method_type = "DinersClub" +[[payme.debit]] + payment_method_type = "Discover" +[[payme.debit]] + payment_method_type = "CartesBancaires" +[[payme.debit]] + payment_method_type = "UnionPay" +[payme.connector_auth.BodyKey] +api_key="Seller Payme Id" +key1="Payme Public Key" +[payme.connector_webhook_details] +merchant_secret="Payme Client Secret" +additional_secret="Payme Client Key" + +[paypal] +[[paypal.credit]] + payment_method_type = "Mastercard" +[[paypal.credit]] + payment_method_type = "Visa" +[[paypal.credit]] + payment_method_type = "Interac" +[[paypal.credit]] + payment_method_type = "AmericanExpress" +[[paypal.credit]] + payment_method_type = "JCB" +[[paypal.credit]] + payment_method_type = "DinersClub" +[[paypal.credit]] + payment_method_type = "Discover" +[[paypal.credit]] + payment_method_type = "CartesBancaires" +[[paypal.credit]] + payment_method_type = "UnionPay" +[[paypal.debit]] + payment_method_type = "Mastercard" +[[paypal.debit]] + payment_method_type = "Visa" +[[paypal.debit]] + payment_method_type = "Interac" +[[paypal.debit]] + payment_method_type = "AmericanExpress" +[[paypal.debit]] + payment_method_type = "JCB" +[[paypal.debit]] + payment_method_type = "DinersClub" +[[paypal.debit]] + payment_method_type = "Discover" +[[paypal.debit]] + payment_method_type = "CartesBancaires" +[[paypal.debit]] + payment_method_type = "UnionPay" +[[paypal.wallet]] + payment_method_type = "paypal" +[[paypal.bank_redirect]] + payment_method_type = "ideal" +[[paypal.bank_redirect]] + payment_method_type = "giropay" +[[paypal.bank_redirect]] + payment_method_type = "sofort" +[[paypal.bank_redirect]] + payment_method_type = "eps" +is_verifiable = true +[paypal.connector_auth.BodyKey] +api_key="Client Secret" +key1="Client ID" +[paypal.connector_webhook_details] +merchant_secret="Source verification key" + + +[payu] +[[payu.credit]] + payment_method_type = "Mastercard" +[[payu.credit]] + payment_method_type = "Visa" +[[payu.credit]] + payment_method_type = "Interac" +[[payu.credit]] + payment_method_type = "AmericanExpress" +[[payu.credit]] + payment_method_type = "JCB" +[[payu.credit]] + payment_method_type = "DinersClub" +[[payu.credit]] + payment_method_type = "Discover" +[[payu.credit]] + payment_method_type = "CartesBancaires" +[[payu.credit]] + payment_method_type = "UnionPay" +[[payu.debit]] + payment_method_type = "Mastercard" +[[payu.debit]] + payment_method_type = "Visa" +[[payu.debit]] + payment_method_type = "Interac" +[[payu.debit]] + payment_method_type = "AmericanExpress" +[[payu.debit]] + payment_method_type = "JCB" +[[payu.debit]] + payment_method_type = "DinersClub" +[[payu.debit]] + payment_method_type = "Discover" +[[payu.debit]] + payment_method_type = "CartesBancaires" +[[payu.debit]] + payment_method_type = "UnionPay" +[[payu.wallet]] + payment_method_type = "google_pay" +[payu.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant POS ID" +[payu.connector_webhook_details] +merchant_secret="Source verification key" + +[payu.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[placetopay] +[[placetopay.credit]] + payment_method_type = "Mastercard" +[[placetopay.credit]] + payment_method_type = "Visa" +[[placetopay.credit]] + payment_method_type = "Interac" +[[placetopay.credit]] + payment_method_type = "AmericanExpress" +[[placetopay.credit]] + payment_method_type = "JCB" +[[placetopay.credit]] + payment_method_type = "DinersClub" +[[placetopay.credit]] + payment_method_type = "Discover" +[[placetopay.credit]] + payment_method_type = "CartesBancaires" +[[placetopay.credit]] + payment_method_type = "UnionPay" +[[placetopay.debit]] + payment_method_type = "Mastercard" +[[placetopay.debit]] + payment_method_type = "Visa" +[[placetopay.debit]] + payment_method_type = "Interac" +[[placetopay.debit]] + payment_method_type = "AmericanExpress" +[[placetopay.debit]] + payment_method_type = "JCB" +[[placetopay.debit]] + payment_method_type = "DinersClub" +[[placetopay.debit]] + payment_method_type = "Discover" +[[placetopay.debit]] + payment_method_type = "CartesBancaires" +[[placetopay.debit]] + payment_method_type = "UnionPay" +[placetopay.connector_auth.BodyKey] +api_key="Login" +key1="Trankey" + +[powertranz] +[[powertranz.credit]] + payment_method_type = "Mastercard" +[[powertranz.credit]] + payment_method_type = "Visa" +[[powertranz.credit]] + payment_method_type = "Interac" +[[powertranz.credit]] + payment_method_type = "AmericanExpress" +[[powertranz.credit]] + payment_method_type = "JCB" +[[powertranz.credit]] + payment_method_type = "DinersClub" +[[powertranz.credit]] + payment_method_type = "Discover" +[[powertranz.credit]] + payment_method_type = "CartesBancaires" +[[powertranz.credit]] + payment_method_type = "UnionPay" +[[powertranz.debit]] + payment_method_type = "Mastercard" +[[powertranz.debit]] + payment_method_type = "Visa" +[[powertranz.debit]] + payment_method_type = "Interac" +[[powertranz.debit]] + payment_method_type = "AmericanExpress" +[[powertranz.debit]] + payment_method_type = "JCB" +[[powertranz.debit]] + payment_method_type = "DinersClub" +[[powertranz.debit]] + payment_method_type = "Discover" +[[powertranz.debit]] + payment_method_type = "CartesBancaires" +[[powertranz.debit]] + payment_method_type = "UnionPay" +[powertranz.connector_auth.BodyKey] +key1 = "PowerTranz Id" +api_key="PowerTranz Password" +[powertranz.connector_webhook_details] +merchant_secret="Source verification key" + +[rapyd] +[[rapyd.credit]] + payment_method_type = "Mastercard" +[[rapyd.credit]] + payment_method_type = "Visa" +[[rapyd.credit]] + payment_method_type = "Interac" +[[rapyd.credit]] + payment_method_type = "AmericanExpress" +[[rapyd.credit]] + payment_method_type = "JCB" +[[rapyd.credit]] + payment_method_type = "DinersClub" +[[rapyd.credit]] + payment_method_type = "Discover" +[[rapyd.credit]] + payment_method_type = "CartesBancaires" +[[rapyd.credit]] + payment_method_type = "UnionPay" +[[rapyd.debit]] + payment_method_type = "Mastercard" +[[rapyd.debit]] + payment_method_type = "Visa" +[[rapyd.debit]] + payment_method_type = "Interac" +[[rapyd.debit]] + payment_method_type = "AmericanExpress" +[[rapyd.debit]] + payment_method_type = "JCB" +[[rapyd.debit]] + payment_method_type = "DinersClub" +[[rapyd.debit]] + payment_method_type = "Discover" +[[rapyd.debit]] + payment_method_type = "CartesBancaires" +[[rapyd.debit]] + payment_method_type = "UnionPay" +[[rapyd.wallet]] + payment_method_type = "apple_pay" +[rapyd.connector_auth.BodyKey] +api_key="Access Key" +key1="API Secret" +[rapyd.connector_webhook_details] +merchant_secret="Source verification key" + +[rapyd.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[rapyd.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[shift4] +[[shift4.credit]] + payment_method_type = "Mastercard" +[[shift4.credit]] + payment_method_type = "Visa" +[[shift4.credit]] + payment_method_type = "Interac" +[[shift4.credit]] + payment_method_type = "AmericanExpress" +[[shift4.credit]] + payment_method_type = "JCB" +[[shift4.credit]] + payment_method_type = "DinersClub" +[[shift4.credit]] + payment_method_type = "Discover" +[[shift4.credit]] + payment_method_type = "CartesBancaires" +[[shift4.credit]] + payment_method_type = "UnionPay" +[[shift4.debit]] + payment_method_type = "Mastercard" +[[shift4.debit]] + payment_method_type = "Visa" +[[shift4.debit]] + payment_method_type = "Interac" +[[shift4.debit]] + payment_method_type = "AmericanExpress" +[[shift4.debit]] + payment_method_type = "JCB" +[[shift4.debit]] + payment_method_type = "DinersClub" +[[shift4.debit]] + payment_method_type = "Discover" +[[shift4.debit]] + payment_method_type = "CartesBancaires" +[[shift4.debit]] + payment_method_type = "UnionPay" +[[shift4.bank_redirect]] + payment_method_type = "ideal" +[[shift4.bank_redirect]] + payment_method_type = "giropay" +[[shift4.bank_redirect]] + payment_method_type = "sofort" +[[shift4.bank_redirect]] + payment_method_type = "eps" +[shift4.connector_auth.HeaderKey] +api_key="API Key" +[shift4.connector_webhook_details] +merchant_secret="Source verification key" + +[stripe] +[[stripe.credit]] + payment_method_type = "Mastercard" +[[stripe.credit]] + payment_method_type = "Visa" +[[stripe.credit]] + payment_method_type = "Interac" +[[stripe.credit]] + payment_method_type = "AmericanExpress" +[[stripe.credit]] + payment_method_type = "JCB" +[[stripe.credit]] + payment_method_type = "DinersClub" +[[stripe.credit]] + payment_method_type = "Discover" +[[stripe.credit]] + payment_method_type = "CartesBancaires" +[[stripe.credit]] + payment_method_type = "UnionPay" +[[stripe.debit]] + payment_method_type = "Mastercard" +[[stripe.debit]] + payment_method_type = "Visa" +[[stripe.debit]] + payment_method_type = "Interac" +[[stripe.debit]] + payment_method_type = "AmericanExpress" +[[stripe.debit]] + payment_method_type = "JCB" +[[stripe.debit]] + payment_method_type = "DinersClub" +[[stripe.debit]] + payment_method_type = "Discover" +[[stripe.debit]] + payment_method_type = "CartesBancaires" +[[stripe.debit]] + payment_method_type = "UnionPay" +[[stripe.pay_later]] + payment_method_type = "klarna" +[[stripe.pay_later]] + payment_method_type = "affirm" +[[stripe.pay_later]] + payment_method_type = "afterpay_clearpay" +[[stripe.bank_redirect]] + payment_method_type = "ideal" +[[stripe.bank_redirect]] + payment_method_type = "giropay" +[[stripe.bank_redirect]] + payment_method_type = "sofort" +[[stripe.bank_redirect]] + payment_method_type = "eps" +[[stripe.bank_redirect]] + payment_method_type = "bancontact_card" +[[stripe.bank_redirect]] + payment_method_type = "przelewy24" +[[stripe.bank_debit]] + payment_method_type = "ach" +[[stripe.bank_debit]] + payment_method_type = "bacs" +[[stripe.bank_debit]] + payment_method_type = "becs" +[[stripe.bank_debit]] + payment_method_type = "sepa" +[[stripe.bank_transfer]] + payment_method_type = "ach" +[[stripe.bank_transfer]] + payment_method_type = "bacs" +[[stripe.bank_transfer]] + payment_method_type = "sepa" +[[stripe.bank_transfer]] + payment_method_type = "multibanco" +[[stripe.wallet]] + payment_method_type = "apple_pay" +[[stripe.wallet]] + payment_method_type = "google_pay" +[[stripe.wallet]] + payment_method_type = "we_chat_pay" +[[stripe.wallet]] + payment_method_type = "ali_pay" +[[stripe.wallet]] + payment_method_type = "cashapp" +is_verifiable = true +[stripe.connector_auth.HeaderKey] +api_key="Secret Key" +[stripe.connector_webhook_details] +merchant_secret="Source verification key" + +[stripe.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +stripe_publishable_key="Stripe Publishable Key" +merchant_id="Google Pay Merchant ID" + +[stripe.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[stripe.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[stax] +[[stax.credit]] + payment_method_type = "Mastercard" +[[stax.credit]] + payment_method_type = "Visa" +[[stax.credit]] + payment_method_type = "Interac" +[[stax.credit]] + payment_method_type = "AmericanExpress" +[[stax.credit]] + payment_method_type = "JCB" +[[stax.credit]] + payment_method_type = "DinersClub" +[[stax.credit]] + payment_method_type = "Discover" +[[stax.credit]] + payment_method_type = "CartesBancaires" +[[stax.credit]] + payment_method_type = "UnionPay" +[[stax.debit]] + payment_method_type = "Mastercard" +[[stax.debit]] + payment_method_type = "Visa" +[[stax.debit]] + payment_method_type = "Interac" +[[stax.debit]] + payment_method_type = "AmericanExpress" +[[stax.debit]] + payment_method_type = "JCB" +[[stax.debit]] + payment_method_type = "DinersClub" +[[stax.debit]] + payment_method_type = "Discover" +[[stax.debit]] + payment_method_type = "CartesBancaires" +[[stax.debit]] + payment_method_type = "UnionPay" +[[stax.bank_debit]] + payment_method_type = "ach" +[stax.connector_auth.HeaderKey] +api_key="Api Key" +[stax.connector_webhook_details] +merchant_secret="Source verification key" + +[square] +[[square.credit]] + payment_method_type = "Mastercard" +[[square.credit]] + payment_method_type = "Visa" +[[square.credit]] + payment_method_type = "Interac" +[[square.credit]] + payment_method_type = "AmericanExpress" +[[square.credit]] + payment_method_type = "JCB" +[[square.credit]] + payment_method_type = "DinersClub" +[[square.credit]] + payment_method_type = "Discover" +[[square.credit]] + payment_method_type = "CartesBancaires" +[[square.credit]] + payment_method_type = "UnionPay" +[[square.debit]] + payment_method_type = "Mastercard" +[[square.debit]] + payment_method_type = "Visa" +[[square.debit]] + payment_method_type = "Interac" +[[square.debit]] + payment_method_type = "AmericanExpress" +[[square.debit]] + payment_method_type = "JCB" +[[square.debit]] + payment_method_type = "DinersClub" +[[square.debit]] + payment_method_type = "Discover" +[[square.debit]] + payment_method_type = "CartesBancaires" +[[square.debit]] + payment_method_type = "UnionPay" +[square_payout.connector_auth.BodyKey] +api_key = "Square API Key" +key1 = "Square Client Id" +[square.connector_webhook_details] +merchant_secret="Source verification key" + +[trustpay] +[[trustpay.credit]] + payment_method_type = "Mastercard" +[[trustpay.credit]] + payment_method_type = "Visa" +[[trustpay.credit]] + payment_method_type = "Interac" +[[trustpay.credit]] + payment_method_type = "AmericanExpress" +[[trustpay.credit]] + payment_method_type = "JCB" +[[trustpay.credit]] + payment_method_type = "DinersClub" +[[trustpay.credit]] + payment_method_type = "Discover" +[[trustpay.credit]] + payment_method_type = "CartesBancaires" +[[trustpay.credit]] + payment_method_type = "UnionPay" +[[trustpay.debit]] + payment_method_type = "Mastercard" +[[trustpay.debit]] + payment_method_type = "Visa" +[[trustpay.debit]] + payment_method_type = "Interac" +[[trustpay.debit]] + payment_method_type = "AmericanExpress" +[[trustpay.debit]] + payment_method_type = "JCB" +[[trustpay.debit]] + payment_method_type = "DinersClub" +[[trustpay.debit]] + payment_method_type = "Discover" +[[trustpay.debit]] + payment_method_type = "CartesBancaires" +[[trustpay.debit]] + payment_method_type = "UnionPay" +[[trustpay.bank_redirect]] + payment_method_type = "ideal" +[[trustpay.bank_redirect]] + payment_method_type = "giropay" +[[trustpay.bank_redirect]] + payment_method_type = "sofort" +[[trustpay.bank_redirect]] + payment_method_type = "eps" +[[trustpay.bank_redirect]] + payment_method_type = "blik" +[[trustpay.wallet]] + payment_method_type = "apple_pay" +[[trustpay.wallet]] + payment_method_type = "google_pay" +[trustpay.connector_auth.SignatureKey] +api_key="API Key" +key1="Project ID" +api_secret="Secret Key" +[trustpay.connector_webhook_details] +merchant_secret="Source verification key" + +[trustpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[trustpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[tsys] +[[tsys.credit]] + payment_method_type = "Mastercard" +[[tsys.credit]] + payment_method_type = "Visa" +[[tsys.credit]] + payment_method_type = "Interac" +[[tsys.credit]] + payment_method_type = "AmericanExpress" +[[tsys.credit]] + payment_method_type = "JCB" +[[tsys.credit]] + payment_method_type = "DinersClub" +[[tsys.credit]] + payment_method_type = "Discover" +[[tsys.credit]] + payment_method_type = "CartesBancaires" +[[tsys.credit]] + payment_method_type = "UnionPay" +[[tsys.debit]] + payment_method_type = "Mastercard" +[[tsys.debit]] + payment_method_type = "Visa" +[[tsys.debit]] + payment_method_type = "Interac" +[[tsys.debit]] + payment_method_type = "AmericanExpress" +[[tsys.debit]] + payment_method_type = "JCB" +[[tsys.debit]] + payment_method_type = "DinersClub" +[[tsys.debit]] + payment_method_type = "Discover" +[[tsys.debit]] + payment_method_type = "CartesBancaires" +[[tsys.debit]] + payment_method_type = "UnionPay" +[tsys.connector_auth.SignatureKey] +api_key="Device Id" +key1="Transaction Key" +api_secret="Developer Id" +[tsys.connector_webhook_details] +merchant_secret="Source verification key" + +[volt] +[[volt.bank_redirect]] + payment_method_type = "open_banking_uk" +[volt.connector_auth.MultiAuthKey] +api_key = "Username" +api_secret = "Password" +key1 = "Client ID" +key2 = "Client Secret" +[volt.connector_webhook_details] +merchant_secret="Source verification key" + +[worldline] +[[worldline.credit]] + payment_method_type = "Mastercard" +[[worldline.credit]] + payment_method_type = "Visa" +[[worldline.credit]] + payment_method_type = "Interac" +[[worldline.credit]] + payment_method_type = "AmericanExpress" +[[worldline.credit]] + payment_method_type = "JCB" +[[worldline.credit]] + payment_method_type = "DinersClub" +[[worldline.credit]] + payment_method_type = "Discover" +[[worldline.credit]] + payment_method_type = "CartesBancaires" +[[worldline.credit]] + payment_method_type = "UnionPay" +[[worldline.debit]] + payment_method_type = "Mastercard" +[[worldline.debit]] + payment_method_type = "Visa" +[[worldline.debit]] + payment_method_type = "Interac" +[[worldline.debit]] + payment_method_type = "AmericanExpress" +[[worldline.debit]] + payment_method_type = "JCB" +[[worldline.debit]] + payment_method_type = "DinersClub" +[[worldline.debit]] + payment_method_type = "Discover" +[[worldline.debit]] + payment_method_type = "CartesBancaires" +[[worldline.debit]] + payment_method_type = "UnionPay" +[[worldline.bank_redirect]] + payment_method_type = "ideal" +[[worldline.bank_redirect]] + payment_method_type = "giropay" +[worldline.connector_auth.SignatureKey] +api_key="API Key ID" +key1="Merchant ID" +api_secret="Secret API Key" +[worldline.connector_webhook_details] +merchant_secret="Source verification key" + +[worldpay] +[[worldpay.credit]] + payment_method_type = "Mastercard" +[[worldpay.credit]] + payment_method_type = "Visa" +[[worldpay.credit]] + payment_method_type = "Interac" +[[worldpay.credit]] + payment_method_type = "AmericanExpress" +[[worldpay.credit]] + payment_method_type = "JCB" +[[worldpay.credit]] + payment_method_type = "DinersClub" +[[worldpay.credit]] + payment_method_type = "Discover" +[[worldpay.credit]] + payment_method_type = "CartesBancaires" +[[worldpay.credit]] + payment_method_type = "UnionPay" +[[worldpay.debit]] + payment_method_type = "Mastercard" +[[worldpay.debit]] + payment_method_type = "Visa" +[[worldpay.debit]] + payment_method_type = "Interac" +[[worldpay.debit]] + payment_method_type = "AmericanExpress" +[[worldpay.debit]] + payment_method_type = "JCB" +[[worldpay.debit]] + payment_method_type = "DinersClub" +[[worldpay.debit]] + payment_method_type = "Discover" +[[worldpay.debit]] + payment_method_type = "CartesBancaires" +[[worldpay.debit]] + payment_method_type = "UnionPay" +[[worldpay.wallet]] + payment_method_type = "google_pay" +[[worldpay.wallet]] + payment_method_type = "apple_pay" +[worldpay.connector_auth.BodyKey] +api_key="Username" +key1="Password" +[worldpay.connector_webhook_details] +merchant_secret="Source verification key" + +[worldpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[worldpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[worldpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[zen] +[[zen.credit]] + payment_method_type = "Mastercard" +[[zen.credit]] + payment_method_type = "Visa" +[[zen.credit]] + payment_method_type = "Interac" +[[zen.credit]] + payment_method_type = "AmericanExpress" +[[zen.credit]] + payment_method_type = "JCB" +[[zen.credit]] + payment_method_type = "DinersClub" +[[zen.credit]] + payment_method_type = "Discover" +[[zen.credit]] + payment_method_type = "CartesBancaires" +[[zen.credit]] + payment_method_type = "UnionPay" +[[zen.debit]] + payment_method_type = "Mastercard" +[[zen.debit]] + payment_method_type = "Visa" +[[zen.debit]] + payment_method_type = "Interac" +[[zen.debit]] + payment_method_type = "AmericanExpress" +[[zen.debit]] + payment_method_type = "JCB" +[[zen.debit]] + payment_method_type = "DinersClub" +[[zen.debit]] + payment_method_type = "Discover" +[[zen.debit]] + payment_method_type = "CartesBancaires" +[[zen.debit]] + payment_method_type = "UnionPay" +[[zen.voucher]] + payment_method_type = "boleto" +[[zen.voucher]] + payment_method_type = "efecty" +[[zen.voucher]] + payment_method_type = "pago_efectivo" +[[zen.voucher]] + payment_method_type = "red_compra" +[[zen.voucher]] + payment_method_type = "red_pagos" +[[zen.bank_transfer]] + payment_method_type = "pix" +[[zen.bank_transfer]] + payment_method_type = "pse" +[[zen.wallet]] + payment_method_type = "apple_pay" +[[zen.wallet]] + payment_method_type = "google_pay" +[zen.connector_auth.HeaderKey] +api_key="API Key" +[zen.connector_webhook_details] +merchant_secret="Source verification key" + +[zen.metadata.apple_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" +[zen.metadata.google_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" + + +[dummy_connector] +[[dummy_connector.credit]] + payment_method_type = "Mastercard" +[[dummy_connector.credit]] + payment_method_type = "Visa" +[[dummy_connector.credit]] + payment_method_type = "Interac" +[[dummy_connector.credit]] + payment_method_type = "AmericanExpress" +[[dummy_connector.credit]] + payment_method_type = "JCB" +[[dummy_connector.credit]] + payment_method_type = "DinersClub" +[[dummy_connector.credit]] + payment_method_type = "Discover" +[[dummy_connector.credit]] + payment_method_type = "CartesBancaires" +[[dummy_connector.credit]] + payment_method_type = "UnionPay" +[[dummy_connector.debit]] + payment_method_type = "Mastercard" +[[dummy_connector.debit]] + payment_method_type = "Visa" +[[dummy_connector.debit]] + payment_method_type = "Interac" +[[dummy_connector.debit]] + payment_method_type = "AmericanExpress" +[[dummy_connector.debit]] + payment_method_type = "JCB" +[[dummy_connector.debit]] + payment_method_type = "DinersClub" +[[dummy_connector.debit]] + payment_method_type = "Discover" +[[dummy_connector.debit]] + payment_method_type = "CartesBancaires" +[[dummy_connector.debit]] + payment_method_type = "UnionPay" +[dummy_connector.connector_auth.HeaderKey] +api_key="Api Key" + +[paypal_test] +[[paypal_test.credit]] + payment_method_type = "Mastercard" +[[paypal_test.credit]] + payment_method_type = "Visa" +[[paypal_test.credit]] + payment_method_type = "Interac" +[[paypal_test.credit]] + payment_method_type = "AmericanExpress" +[[paypal_test.credit]] + payment_method_type = "JCB" +[[paypal_test.credit]] + payment_method_type = "DinersClub" +[[paypal_test.credit]] + payment_method_type = "Discover" +[[paypal_test.credit]] + payment_method_type = "CartesBancaires" +[[paypal_test.credit]] + payment_method_type = "UnionPay" +[[paypal_test.debit]] + payment_method_type = "Mastercard" +[[paypal_test.debit]] + payment_method_type = "Visa" +[[paypal_test.debit]] + payment_method_type = "Interac" +[[paypal_test.debit]] + payment_method_type = "AmericanExpress" +[[paypal_test.debit]] + payment_method_type = "JCB" +[[paypal_test.debit]] + payment_method_type = "DinersClub" +[[paypal_test.debit]] + payment_method_type = "Discover" +[[paypal_test.debit]] + payment_method_type = "CartesBancaires" +[[paypal_test.debit]] + payment_method_type = "UnionPay" +[[paypal_test.wallet]] + payment_method_type = "paypal" +[paypal_test.connector_auth.HeaderKey] +api_key="Api Key" + +[stripe_test] +[[stripe_test.credit]] + payment_method_type = "Mastercard" +[[stripe_test.credit]] + payment_method_type = "Visa" +[[stripe_test.credit]] + payment_method_type = "Interac" +[[stripe_test.credit]] + payment_method_type = "AmericanExpress" +[[stripe_test.credit]] + payment_method_type = "JCB" +[[stripe_test.credit]] + payment_method_type = "DinersClub" +[[stripe_test.credit]] + payment_method_type = "Discover" +[[stripe_test.credit]] + payment_method_type = "CartesBancaires" +[[stripe_test.credit]] + payment_method_type = "UnionPay" +[[stripe_test.debit]] + payment_method_type = "Mastercard" +[[stripe_test.debit]] + payment_method_type = "Visa" +[[stripe_test.debit]] + payment_method_type = "Interac" +[[stripe_test.debit]] + payment_method_type = "AmericanExpress" +[[stripe_test.debit]] + payment_method_type = "JCB" +[[stripe_test.debit]] + payment_method_type = "DinersClub" +[[stripe_test.debit]] + payment_method_type = "Discover" +[[stripe_test.debit]] + payment_method_type = "CartesBancaires" +[[stripe_test.debit]] + payment_method_type = "UnionPay" +[[stripe_test.wallet]] + payment_method_type = "google_pay" +[[stripe_test.wallet]] + payment_method_type = "ali_pay" +[[stripe_test.wallet]] + payment_method_type = "we_chat_pay" +[[stripe_test.pay_later]] + payment_method_type = "klarna" +[[stripe_test.pay_later]] + payment_method_type = "affirm" +[[stripe_test.pay_later]] + payment_method_type = "afterpay_clearpay" +[stripe_test.connector_auth.HeaderKey] +api_key="Api Key" + +[helcim] +[[helcim.credit]] + payment_method_type = "Mastercard" +[[helcim.credit]] + payment_method_type = "Visa" +[[helcim.credit]] + payment_method_type = "Interac" +[[helcim.credit]] + payment_method_type = "AmericanExpress" +[[helcim.credit]] + payment_method_type = "JCB" +[[helcim.credit]] + payment_method_type = "DinersClub" +[[helcim.credit]] + payment_method_type = "Discover" +[[helcim.credit]] + payment_method_type = "CartesBancaires" +[[helcim.credit]] + payment_method_type = "UnionPay" +[[helcim.debit]] + payment_method_type = "Mastercard" +[[helcim.debit]] + payment_method_type = "Visa" +[[helcim.debit]] + payment_method_type = "Interac" +[[helcim.debit]] + payment_method_type = "AmericanExpress" +[[helcim.debit]] + payment_method_type = "JCB" +[[helcim.debit]] + payment_method_type = "DinersClub" +[[helcim.debit]] + payment_method_type = "Discover" +[[helcim.debit]] + payment_method_type = "CartesBancaires" +[[helcim.debit]] + payment_method_type = "UnionPay" +[helcim.connector_auth.HeaderKey] +api_key="Api Key" + + + + +[adyen_payout] +[[adyen_payout.credit]] + payment_method_type = "Mastercard" +[[adyen_payout.credit]] + payment_method_type = "Visa" +[[adyen_payout.credit]] + payment_method_type = "Interac" +[[adyen_payout.credit]] + payment_method_type = "AmericanExpress" +[[adyen_payout.credit]] + payment_method_type = "JCB" +[[adyen_payout.credit]] + payment_method_type = "DinersClub" +[[adyen_payout.credit]] + payment_method_type = "Discover" +[[adyen_payout.credit]] + payment_method_type = "CartesBancaires" +[[adyen_payout.credit]] + payment_method_type = "UnionPay" +[[adyen_payout.debit]] + payment_method_type = "Mastercard" +[[adyen_payout.debit]] + payment_method_type = "Visa" +[[adyen_payout.debit]] + payment_method_type = "Interac" +[[adyen_payout.debit]] + payment_method_type = "AmericanExpress" +[[adyen_payout.debit]] + payment_method_type = "JCB" +[[adyen_payout.debit]] + payment_method_type = "DinersClub" +[[adyen_payout.debit]] + payment_method_type = "Discover" +[[adyen_payout.debit]] + payment_method_type = "CartesBancaires" +[[adyen_payout.debit]] + payment_method_type = "UnionPay" +[[adyen_payout.bank_transfer]] + payment_method_type = "ach" +[[adyen_payout.bank_transfer]] + payment_method_type = "bacs" +[[adyen_payout.bank_transfer]] + payment_method_type = "sepa" +[adyen_payout.connector_auth.SignatureKey] +api_key = "Adyen API Key (Payout creation)" +api_secret = "Adyen Key (Payout submission)" +key1 = "Adyen Account Id" + +[wise_payout] +[[wise_payout.bank_transfer]] + payment_method_type = "ach" +[[wise_payout.bank_transfer]] + payment_method_type = "bacs" +[[wise_payout.bank_transfer]] + payment_method_type = "sepa" +[wise_payout.connector_auth.BodyKey] +api_key = "Wise API Key" +key1 = "Wise Account Id" \ No newline at end of file diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml new file mode 100644 index 00000000000..e837314f610 --- /dev/null +++ b/crates/connector_configs/toml/production.toml @@ -0,0 +1,1799 @@ +[aci] +[[aci.credit]] + payment_method_type = "Mastercard" +[[aci.credit]] + payment_method_type = "Visa" +[[aci.credit]] + payment_method_type = "Interac" +[[aci.credit]] + payment_method_type = "AmericanExpress" +[[aci.credit]] + payment_method_type = "JCB" +[[aci.credit]] + payment_method_type = "DinersClub" +[[aci.credit]] + payment_method_type = "Discover" +[[aci.credit]] + payment_method_type = "CartesBancaires" +[[aci.credit]] + payment_method_type = "UnionPay" +[[aci.debit]] + payment_method_type = "Mastercard" +[[aci.debit]] + payment_method_type = "Visa" +[[aci.debit]] + payment_method_type = "Interac" +[[aci.debit]] + payment_method_type = "AmericanExpress" +[[aci.debit]] + payment_method_type = "JCB" +[[aci.debit]] + payment_method_type = "DinersClub" +[[aci.debit]] + payment_method_type = "Discover" +[[aci.debit]] + payment_method_type = "CartesBancaires" +[[aci.debit]] + payment_method_type = "UnionPay" +[[aci.wallet]] + payment_method_type = "ali_pay" +[[aci.wallet]] + payment_method_type = "mb_way" +[[aci.bank_redirect]] + payment_method_type = "ideal" +[[aci.bank_redirect]] + payment_method_type = "giropay" +[[aci.bank_redirect]] + payment_method_type = "sofort" +[[aci.bank_redirect]] + payment_method_type = "eps" +[[aci.bank_redirect]] + payment_method_type = "przelewy24" +[[aci.bank_redirect]] + payment_method_type = "trustly" +[aci.connector_auth.BodyKey] +api_key="API Key" +key1="Entity ID" +[aci.connector_webhook_details] +merchant_secret="Source verification key" + +[adyen] +[[adyen.credit]] + payment_method_type = "Mastercard" +[[adyen.credit]] + payment_method_type = "Visa" +[[adyen.credit]] + payment_method_type = "Interac" +[[adyen.credit]] + payment_method_type = "AmericanExpress" +[[adyen.credit]] + payment_method_type = "JCB" +[[adyen.credit]] + payment_method_type = "DinersClub" +[[adyen.credit]] + payment_method_type = "Discover" +[[adyen.credit]] + payment_method_type = "CartesBancaires" +[[adyen.credit]] + payment_method_type = "UnionPay" +[[adyen.debit]] + payment_method_type = "Mastercard" +[[adyen.debit]] + payment_method_type = "Visa" +[[adyen.debit]] + payment_method_type = "Interac" +[[adyen.debit]] + payment_method_type = "AmericanExpress" +[[adyen.debit]] + payment_method_type = "JCB" +[[adyen.debit]] + payment_method_type = "DinersClub" +[[adyen.debit]] + payment_method_type = "Discover" +[[adyen.debit]] + payment_method_type = "CartesBancaires" +[[adyen.debit]] + payment_method_type = "UnionPay" +[[adyen.pay_later]] + payment_method_type = "klarna" +[[adyen.pay_later]] + payment_method_type = "affirm" +[[adyen.pay_later]] + payment_method_type = "afterpay_clearpay" +[[adyen.bank_debit]] + payment_method_type = "ach" +[[adyen.bank_debit]] + payment_method_type = "bacs" +[[adyen.bank_redirect]] + payment_method_type = "ideal" +[[adyen.bank_redirect]] + payment_method_type = "giropay" +[[adyen.bank_redirect]] + payment_method_type = "sofort" +[[adyen.bank_redirect]] + payment_method_type = "eps" +[[adyen.wallet]] + payment_method_type = "apple_pay" +[[adyen.wallet]] + payment_method_type = "google_pay" +[[adyen.wallet]] + payment_method_type = "paypal" +[adyen.connector_auth.BodyKey] +api_key="Adyen API Key" +key1="Adyen Account Id" +[adyen.connector_webhook_details] +merchant_secret="Source verification key" + +[adyen.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[adyen.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[adyen.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + + + +[airwallex] +[[airwallex.credit]] + payment_method_type = "Mastercard" +[[airwallex.credit]] + payment_method_type = "Visa" +[[airwallex.credit]] + payment_method_type = "Interac" +[[airwallex.credit]] + payment_method_type = "AmericanExpress" +[[airwallex.credit]] + payment_method_type = "JCB" +[[airwallex.credit]] + payment_method_type = "DinersClub" +[[airwallex.credit]] + payment_method_type = "Discover" +[[airwallex.credit]] + payment_method_type = "CartesBancaires" +[[airwallex.credit]] + payment_method_type = "UnionPay" +[[airwallex.debit]] + payment_method_type = "Mastercard" +[[airwallex.debit]] + payment_method_type = "Visa" +[[airwallex.debit]] + payment_method_type = "Interac" +[[airwallex.debit]] + payment_method_type = "AmericanExpress" +[[airwallex.debit]] + payment_method_type = "JCB" +[[airwallex.debit]] + payment_method_type = "DinersClub" +[[airwallex.debit]] + payment_method_type = "Discover" +[[airwallex.debit]] + payment_method_type = "CartesBancaires" +[[airwallex.debit]] + payment_method_type = "UnionPay" +body_type="BodyKey" +[airwallex.connector_auth.BodyKey] +api_key="API Key" +key1="Client ID" +[airwallex.connector_webhook_details] +merchant_secret="Source verification key" + +[authorizedotnet] +[[authorizedotnet.credit]] + payment_method_type = "Mastercard" +[[authorizedotnet.credit]] + payment_method_type = "Visa" +[[authorizedotnet.credit]] + payment_method_type = "Interac" +[[authorizedotnet.credit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.credit]] + payment_method_type = "JCB" +[[authorizedotnet.credit]] + payment_method_type = "DinersClub" +[[authorizedotnet.credit]] + payment_method_type = "Discover" +[[authorizedotnet.credit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.credit]] + payment_method_type = "UnionPay" +[[authorizedotnet.debit]] + payment_method_type = "Mastercard" +[[authorizedotnet.debit]] + payment_method_type = "Visa" +[[authorizedotnet.debit]] + payment_method_type = "Interac" +[[authorizedotnet.debit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.debit]] + payment_method_type = "JCB" +[[authorizedotnet.debit]] + payment_method_type = "DinersClub" +[[authorizedotnet.debit]] + payment_method_type = "Discover" +[[authorizedotnet.debit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.debit]] + payment_method_type = "UnionPay" +[[authorizedotnet.wallet]] + payment_method_type = "google_pay" +[[authorizedotnet.wallet]] + payment_method_type = "paypal" +body_type="BodyKey" +[authorizedotnet.connector_auth.BodyKey] +api_key="API Login ID" +key1="Transaction Key" +[authorizedotnet.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" +[authorizedotnet.connector_webhook_details] +merchant_secret="Source verification key" + +[bitpay] +[[bitpay.crypto]] + payment_method_type = "crypto_currency" +[bitpay.connector_auth.HeaderKey] +api_key="API Key" +[bitpay.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap] +[[bluesnap.credit]] + payment_method_type = "Mastercard" +[[bluesnap.credit]] + payment_method_type = "Visa" +[[bluesnap.credit]] + payment_method_type = "Interac" +[[bluesnap.credit]] + payment_method_type = "AmericanExpress" +[[bluesnap.credit]] + payment_method_type = "JCB" +[[bluesnap.credit]] + payment_method_type = "DinersClub" +[[bluesnap.credit]] + payment_method_type = "Discover" +[[bluesnap.credit]] + payment_method_type = "CartesBancaires" +[[bluesnap.credit]] + payment_method_type = "UnionPay" +[[bluesnap.debit]] + payment_method_type = "Mastercard" +[[bluesnap.debit]] + payment_method_type = "Visa" +[[bluesnap.debit]] + payment_method_type = "Interac" +[[bluesnap.debit]] + payment_method_type = "AmericanExpress" +[[bluesnap.debit]] + payment_method_type = "JCB" +[[bluesnap.debit]] + payment_method_type = "DinersClub" +[[bluesnap.debit]] + payment_method_type = "Discover" +[[bluesnap.debit]] + payment_method_type = "CartesBancaires" +[[bluesnap.debit]] + payment_method_type = "UnionPay" +[[bluesnap.wallet]] + payment_method_type = "google_pay" +[[bluesnap.wallet]] + payment_method_type = "apple_pay" +[bluesnap.connector_auth.BodyKey] +api_key="Password" +key1="Username" +[bluesnap.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[bluesnap.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bluesnap.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" +[bluesnap.metadata] +merchant_id="Merchant Id" + +[braintree] +[[braintree.credit]] + payment_method_type = "Mastercard" +[[braintree.credit]] + payment_method_type = "Visa" +[[braintree.credit]] + payment_method_type = "Interac" +[[braintree.credit]] + payment_method_type = "AmericanExpress" +[[braintree.credit]] + payment_method_type = "JCB" +[[braintree.credit]] + payment_method_type = "DinersClub" +[[braintree.credit]] + payment_method_type = "Discover" +[[braintree.credit]] + payment_method_type = "CartesBancaires" +[[braintree.credit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "Mastercard" +[[braintree.debit]] + payment_method_type = "Visa" +[[braintree.debit]] + payment_method_type = "Interac" +[[braintree.debit]] + payment_method_type = "AmericanExpress" +[[braintree.debit]] + payment_method_type = "JCB" +[[braintree.debit]] + payment_method_type = "DinersClub" +[[braintree.debit]] + payment_method_type = "Discover" +[[braintree.debit]] + payment_method_type = "CartesBancaires" +[[bluesnap.debit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "UnionPay" + +[braintree.connector_auth.SignatureKey] +api_key="Public Key" +key1="Merchant Id" +api_secret="Private Key" +[braintree.connector_webhook_details] +merchant_secret="Source verification key" +[braintree.metadata] +merchant_account_id="Merchant Account Id" +merchant_config_currency="Currency" + +[bambora] +[[bambora.credit]] + payment_method_type = "Mastercard" +[[bambora.credit]] + payment_method_type = "Visa" +[[bambora.credit]] + payment_method_type = "Interac" +[[bambora.credit]] + payment_method_type = "AmericanExpress" +[[bambora.credit]] + payment_method_type = "JCB" +[[bambora.credit]] + payment_method_type = "DinersClub" +[[bambora.credit]] + payment_method_type = "Discover" +[[bambora.credit]] + payment_method_type = "CartesBancaires" +[[bambora.credit]] + payment_method_type = "UnionPay" +[[bambora.debit]] + payment_method_type = "Mastercard" +[[bambora.debit]] + payment_method_type = "Visa" +[[bambora.debit]] + payment_method_type = "Interac" +[[bambora.debit]] + payment_method_type = "AmericanExpress" +[[bambora.debit]] + payment_method_type = "JCB" +[[bambora.debit]] + payment_method_type = "DinersClub" +[[bambora.debit]] + payment_method_type = "Discover" +[[bambora.debit]] + payment_method_type = "CartesBancaires" +[[bambora.debit]] + payment_method_type = "UnionPay" +[[bambora.wallet]] + payment_method_type = "apple_pay" +[[bambora.wallet]] + payment_method_type = "paypal" +[bambora.connector_auth.BodyKey] +api_key="Passcode" +key1="Merchant Id" +[bambora.connector_webhook_details] +merchant_secret="Source verification key" + +[bambora.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bambora.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica] +[[bankofamerica.credit]] + payment_method_type = "Mastercard" +[[bankofamerica.credit]] + payment_method_type = "Visa" +[[bankofamerica.credit]] + payment_method_type = "Interac" +[[bankofamerica.credit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.credit]] + payment_method_type = "JCB" +[[bankofamerica.credit]] + payment_method_type = "DinersClub" +[[bankofamerica.credit]] + payment_method_type = "Discover" +[[bankofamerica.credit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.credit]] + payment_method_type = "UnionPay" +[[bankofamerica.debit]] + payment_method_type = "Mastercard" +[[bankofamerica.debit]] + payment_method_type = "Visa" +[[bankofamerica.debit]] + payment_method_type = "Interac" +[[bankofamerica.debit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.debit]] + payment_method_type = "JCB" +[[bankofamerica.debit]] + payment_method_type = "DinersClub" +[[bankofamerica.debit]] + payment_method_type = "Discover" +[[bankofamerica.debit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.debit]] + payment_method_type = "UnionPay" +[[bankofamerica.wallet]] + payment_method_type = "apple_pay" +[[bankofamerica.wallet]] + payment_method_type = "google_pay" + +[bankofamerica.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[bankofamerica.connector_webhook_details] +merchant_secret="Source verification key" + +[bankofamerica.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bankofamerica.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[cashtocode] +[[cashtocode.reward]] + payment_method_type = "classic" +[[cashtocode.reward]] + payment_method_type = "evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_webhook_details] +merchant_secret="Source verification key" + +[cryptopay] +[[cryptopay.crypto]] + payment_method_type = "crypto_currency" +[cryptopay.connector_auth.BodyKey] +api_key="API Key" +key1="Secret Key" +[cryptopay.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout] +[[checkout.credit]] + payment_method_type = "Mastercard" +[[checkout.credit]] + payment_method_type = "Visa" +[[checkout.credit]] + payment_method_type = "Interac" +[[checkout.credit]] + payment_method_type = "AmericanExpress" +[[checkout.credit]] + payment_method_type = "JCB" +[[checkout.credit]] + payment_method_type = "DinersClub" +[[checkout.credit]] + payment_method_type = "Discover" +[[checkout.credit]] + payment_method_type = "CartesBancaires" +[[checkout.credit]] + payment_method_type = "UnionPay" +[[checkout.debit]] + payment_method_type = "Mastercard" +[[checkout.debit]] + payment_method_type = "Visa" +[[checkout.debit]] + payment_method_type = "Interac" +[[checkout.debit]] + payment_method_type = "AmericanExpress" +[[checkout.debit]] + payment_method_type = "JCB" +[[checkout.debit]] + payment_method_type = "DinersClub" +[[checkout.debit]] + payment_method_type = "Discover" +[[checkout.debit]] + payment_method_type = "CartesBancaires" +[[checkout.debit]] + payment_method_type = "UnionPay" +[[checkout.wallet]] + payment_method_type = "apple_pay" +[[checkout.wallet]] + payment_method_type = "google_pay" +[checkout.connector_auth.SignatureKey] +api_key="Checkout API Public Key" +key1="Processing Channel ID" +api_secret="Checkout API Secret Key" +[checkout.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[checkout.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[checkout.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + + + +[coinbase] +[[coinbase.crypto]] + payment_method_type = "crypto_currency" +[coinbase.connector_auth.HeaderKey] +api_key="API Key" +[coinbase.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource] +[[cybersource.credit]] + payment_method_type = "Mastercard" +[[cybersource.credit]] + payment_method_type = "Visa" +[[cybersource.credit]] + payment_method_type = "Interac" +[[cybersource.credit]] + payment_method_type = "AmericanExpress" +[[cybersource.credit]] + payment_method_type = "JCB" +[[cybersource.credit]] + payment_method_type = "DinersClub" +[[cybersource.credit]] + payment_method_type = "Discover" +[[cybersource.credit]] + payment_method_type = "CartesBancaires" +[[cybersource.credit]] + payment_method_type = "UnionPay" +[[cybersource.debit]] + payment_method_type = "Mastercard" +[[cybersource.debit]] + payment_method_type = "Visa" +[[cybersource.debit]] + payment_method_type = "Interac" +[[cybersource.debit]] + payment_method_type = "AmericanExpress" +[[cybersource.debit]] + payment_method_type = "JCB" +[[cybersource.debit]] + payment_method_type = "DinersClub" +[[cybersource.debit]] + payment_method_type = "Discover" +[[cybersource.debit]] + payment_method_type = "CartesBancaires" +[[cybersource.debit]] + payment_method_type = "UnionPay" +[[cybersource.wallet]] + payment_method_type = "apple_pay" +[[cybersource.wallet]] + payment_method_type = "google_pay" +[cybersource.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[cybersource.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[cybersource.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[cybersource.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[dlocal] +[[dlocal.credit]] + payment_method_type = "Mastercard" +[[dlocal.credit]] + payment_method_type = "Visa" +[[dlocal.credit]] + payment_method_type = "Interac" +[[dlocal.credit]] + payment_method_type = "AmericanExpress" +[[dlocal.credit]] + payment_method_type = "JCB" +[[dlocal.credit]] + payment_method_type = "DinersClub" +[[dlocal.credit]] + payment_method_type = "Discover" +[[dlocal.credit]] + payment_method_type = "CartesBancaires" +[[dlocal.credit]] + payment_method_type = "UnionPay" +[[dlocal.debit]] + payment_method_type = "Mastercard" +[[dlocal.debit]] + payment_method_type = "Visa" +[[dlocal.debit]] + payment_method_type = "Interac" +[[dlocal.debit]] + payment_method_type = "AmericanExpress" +[[dlocal.debit]] + payment_method_type = "JCB" +[[dlocal.debit]] + payment_method_type = "DinersClub" +[[dlocal.debit]] + payment_method_type = "Discover" +[[dlocal.debit]] + payment_method_type = "CartesBancaires" +[[dlocal.debit]] + payment_method_type = "UnionPay" +[dlocal.connector_auth.SignatureKey] +api_key="X Login" +key1="X Trans Key" +api_secret="Secret Key" +[dlocal.connector_webhook_details] +merchant_secret="Source verification key" + + +[fiserv] +[[fiserv.credit]] + payment_method_type = "Mastercard" +[[fiserv.credit]] + payment_method_type = "Visa" +[[fiserv.credit]] + payment_method_type = "Interac" +[[fiserv.credit]] + payment_method_type = "AmericanExpress" +[[fiserv.credit]] + payment_method_type = "JCB" +[[fiserv.credit]] + payment_method_type = "DinersClub" +[[fiserv.credit]] + payment_method_type = "Discover" +[[fiserv.credit]] + payment_method_type = "CartesBancaires" +[[fiserv.credit]] + payment_method_type = "UnionPay" +[[fiserv.debit]] + payment_method_type = "Mastercard" +[[fiserv.debit]] + payment_method_type = "Visa" +[[fiserv.debit]] + payment_method_type = "Interac" +[[fiserv.debit]] + payment_method_type = "AmericanExpress" +[[fiserv.debit]] + payment_method_type = "JCB" +[[fiserv.debit]] + payment_method_type = "DinersClub" +[[fiserv.debit]] + payment_method_type = "Discover" +[[fiserv.debit]] + payment_method_type = "CartesBancaires" +[[fiserv.debit]] + payment_method_type = "UnionPay" +[fiserv.connector_auth.SignatureKey] +api_key="API Key" +key1="Merchant ID" +api_secret="API Secret" +[fiserv.connector_webhook_details] +merchant_secret="Source verification key" +[fiserv.metadata] +terminal_id="Terminal ID" + +[forte] +[[forte.credit]] + payment_method_type = "Mastercard" +[[forte.credit]] + payment_method_type = "Visa" +[[forte.credit]] + payment_method_type = "Interac" +[[forte.credit]] + payment_method_type = "AmericanExpress" +[[forte.credit]] + payment_method_type = "JCB" +[[forte.credit]] + payment_method_type = "DinersClub" +[[forte.credit]] + payment_method_type = "Discover" +[[forte.credit]] + payment_method_type = "CartesBancaires" +[[forte.credit]] + payment_method_type = "UnionPay" +[[forte.debit]] + payment_method_type = "Mastercard" +[[forte.debit]] + payment_method_type = "Visa" +[[forte.debit]] + payment_method_type = "Interac" +[[forte.debit]] + payment_method_type = "AmericanExpress" +[[forte.debit]] + payment_method_type = "JCB" +[[forte.debit]] + payment_method_type = "DinersClub" +[[forte.debit]] + payment_method_type = "Discover" +[[forte.debit]] + payment_method_type = "CartesBancaires" +[[forte.debit]] + payment_method_type = "UnionPay" +[forte.connector_auth.MultiAuthKey] +api_key="API Access ID" +key1="Organization ID" +api_secret="API Secure Key" +key2="Location ID" +[forte.connector_webhook_details] +merchant_secret="Source verification key" + + +[globalpay] +[[globalpay.credit]] + payment_method_type = "Mastercard" +[[globalpay.credit]] + payment_method_type = "Visa" +[[globalpay.credit]] + payment_method_type = "Interac" +[[globalpay.credit]] + payment_method_type = "AmericanExpress" +[[globalpay.credit]] + payment_method_type = "JCB" +[[globalpay.credit]] + payment_method_type = "DinersClub" +[[globalpay.credit]] + payment_method_type = "Discover" +[[globalpay.credit]] + payment_method_type = "CartesBancaires" +[[globalpay.credit]] + payment_method_type = "UnionPay" +[[globalpay.debit]] + payment_method_type = "Mastercard" +[[globalpay.debit]] + payment_method_type = "Visa" +[[globalpay.debit]] + payment_method_type = "Interac" +[[globalpay.debit]] + payment_method_type = "AmericanExpress" +[[globalpay.debit]] + payment_method_type = "JCB" +[[globalpay.debit]] + payment_method_type = "DinersClub" +[[globalpay.debit]] + payment_method_type = "Discover" +[[globalpay.debit]] + payment_method_type = "CartesBancaires" +[[globalpay.debit]] + payment_method_type = "UnionPay" +[[globalpay.bank_redirect]] + payment_method_type = "ideal" +[[globalpay.bank_redirect]] + payment_method_type = "giropay" +[[globalpay.bank_redirect]] + payment_method_type = "sofort" +[[globalpay.bank_redirect]] + payment_method_type = "eps" +[[globalpay.wallet]] + payment_method_type = "google_pay" +[[globalpay.wallet]] + payment_method_type = "paypal" +[globalpay.connector_auth.BodyKey] +api_key="Global App Key" +key1="Global App ID" +[globalpay.connector_webhook_details] +merchant_secret="Source verification key" +[globalpay.metadata] +account_name="Account Name" +[globalpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + + +[globepay] +[[globepay.wallet]] + payment_method_type = "we_chat_pay" +[[globepay.wallet]] + payment_method_type = "ali_pay" +[globepay.connector_auth.BodyKey] +api_key="Partner Code" +key1="Credential Code" +[globepay.connector_webhook_details] +merchant_secret="Source verification key" + +[iatapay] +[[iatapay.upi]] + payment_method_type = "upi_collect" +[iatapay.connector_auth.SignatureKey] +api_key="Client ID" +key1="Airline ID" +api_secret="Client Secret" +[iatapay.connector_webhook_details] +merchant_secret="Source verification key" + +[klarna] +[[klarna.pay_later]] + payment_method_type = "klarna" +[klarna.connector_auth.HeaderKey] +api_key="Klarna API Key" +[klarna.connector_webhook_details] +merchant_secret="Source verification key" + + +[mollie] +[[mollie.credit]] + payment_method_type = "Mastercard" +[[mollie.credit]] + payment_method_type = "Visa" +[[mollie.credit]] + payment_method_type = "Interac" +[[mollie.credit]] + payment_method_type = "AmericanExpress" +[[mollie.credit]] + payment_method_type = "JCB" +[[mollie.credit]] + payment_method_type = "DinersClub" +[[mollie.credit]] + payment_method_type = "Discover" +[[mollie.credit]] + payment_method_type = "CartesBancaires" +[[mollie.credit]] + payment_method_type = "UnionPay" +[[mollie.debit]] + payment_method_type = "Mastercard" +[[mollie.debit]] + payment_method_type = "Visa" +[[mollie.debit]] + payment_method_type = "Interac" +[[mollie.debit]] + payment_method_type = "AmericanExpress" +[[mollie.debit]] + payment_method_type = "JCB" +[[mollie.debit]] + payment_method_type = "DinersClub" +[[mollie.debit]] + payment_method_type = "Discover" +[[mollie.debit]] + payment_method_type = "CartesBancaires" +[[mollie.debit]] + payment_method_type = "UnionPay" +[[mollie.bank_redirect]] + payment_method_type = "ideal" +[[mollie.bank_redirect]] + payment_method_type = "giropay" +[[mollie.bank_redirect]] + payment_method_type = "sofort" +[[mollie.bank_redirect]] + payment_method_type = "eps" +[[mollie.wallet]] + payment_method_type = "paypal" +[mollie.connector_auth.BodyKey] +api_key="API Key" +key1="Profile Token" +[mollie.connector_webhook_details] +merchant_secret="Source verification key" + +[multisafepay] +[[multisafepay.credit]] + payment_method_type = "Mastercard" +[[multisafepay.credit]] + payment_method_type = "Visa" +[[multisafepay.credit]] + payment_method_type = "Interac" +[[multisafepay.credit]] + payment_method_type = "AmericanExpress" +[[multisafepay.credit]] + payment_method_type = "JCB" +[[multisafepay.credit]] + payment_method_type = "DinersClub" +[[multisafepay.credit]] + payment_method_type = "Discover" +[[multisafepay.credit]] + payment_method_type = "CartesBancaires" +[[multisafepay.credit]] + payment_method_type = "UnionPay" +[[multisafepay.debit]] + payment_method_type = "Mastercard" +[[multisafepay.debit]] + payment_method_type = "Visa" +[[multisafepay.debit]] + payment_method_type = "Interac" +[[multisafepay.debit]] + payment_method_type = "AmericanExpress" +[[multisafepay.debit]] + payment_method_type = "JCB" +[[multisafepay.debit]] + payment_method_type = "DinersClub" +[[multisafepay.debit]] + payment_method_type = "Discover" +[[multisafepay.debit]] + payment_method_type = "CartesBancaires" +[[multisafepay.debit]] + payment_method_type = "UnionPay" +[multisafepay.connector_auth.HeaderKey] +api_key="Enter API Key" +[multisafepay.connector_webhook_details] +merchant_secret="Source verification key" + + +[nexinets] +[[nexinets.credit]] + payment_method_type = "Mastercard" +[[nexinets.credit]] + payment_method_type = "Visa" +[[nexinets.credit]] + payment_method_type = "Interac" +[[nexinets.credit]] + payment_method_type = "AmericanExpress" +[[nexinets.credit]] + payment_method_type = "JCB" +[[nexinets.credit]] + payment_method_type = "DinersClub" +[[nexinets.credit]] + payment_method_type = "Discover" +[[nexinets.credit]] + payment_method_type = "CartesBancaires" +[[nexinets.credit]] + payment_method_type = "UnionPay" +[[nexinets.debit]] + payment_method_type = "Mastercard" +[[nexinets.debit]] + payment_method_type = "Visa" +[[nexinets.debit]] + payment_method_type = "Interac" +[[nexinets.debit]] + payment_method_type = "AmericanExpress" +[[nexinets.debit]] + payment_method_type = "JCB" +[[nexinets.debit]] + payment_method_type = "DinersClub" +[[nexinets.debit]] + payment_method_type = "Discover" +[[nexinets.debit]] + payment_method_type = "CartesBancaires" +[[nexinets.debit]] + payment_method_type = "UnionPay" +[[nexinets.bank_redirect]] + payment_method_type = "ideal" +[[nexinets.bank_redirect]] + payment_method_type = "giropay" +[[nexinets.bank_redirect]] + payment_method_type = "sofort" +[[nexinets.bank_redirect]] + payment_method_type = "eps" +[[nexinets.wallet]] + payment_method_type = "apple_pay" +[[nexinets.wallet]] + payment_method_type = "paypal" +[nexinets.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant ID" +[nexinets.connector_webhook_details] +merchant_secret="Source verification key" +[nexinets.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nexinets.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[nmi] +[[nmi.bank_redirect]] + payment_method_type = "ideal" +[nmi.connector_auth.BodyKey] +api_key="API Key" +key1="Public Key" +[nmi.connector_webhook_details] +merchant_secret="Source verification key" + +[nuvei] +[[nuvei.credit]] + payment_method_type = "Mastercard" +[[nuvei.credit]] + payment_method_type = "Visa" +[[nuvei.credit]] + payment_method_type = "Interac" +[[nuvei.credit]] + payment_method_type = "AmericanExpress" +[[nuvei.credit]] + payment_method_type = "JCB" +[[nuvei.credit]] + payment_method_type = "DinersClub" +[[nuvei.credit]] + payment_method_type = "Discover" +[[nuvei.credit]] + payment_method_type = "CartesBancaires" +[[nuvei.credit]] + payment_method_type = "UnionPay" +[[nuvei.debit]] + payment_method_type = "Mastercard" +[[nuvei.debit]] + payment_method_type = "Visa" +[[nuvei.debit]] + payment_method_type = "Interac" +[[nuvei.debit]] + payment_method_type = "AmericanExpress" +[[nuvei.debit]] + payment_method_type = "JCB" +[[nuvei.debit]] + payment_method_type = "DinersClub" +[[nuvei.debit]] + payment_method_type = "Discover" +[[nuvei.debit]] + payment_method_type = "CartesBancaires" +[[nuvei.debit]] + payment_method_type = "UnionPay" +[nuvei.connector_auth.SignatureKey] +api_key="Merchant ID" +key1="Merchant Site ID" +api_secret="Merchant Secret" +[nuvei.connector_webhook_details] +merchant_secret="Source verification key" + + + + +[opennode] +[[opennode.crypto]] + payment_method_type = "crypto_currency" +[opennode.connector_auth.HeaderKey] +api_key="API Key" +[opennode.connector_webhook_details] +merchant_secret="Source verification key" + +[paypal] +[[paypal.credit]] + payment_method_type = "Mastercard" +[[paypal.credit]] + payment_method_type = "Visa" +[[paypal.credit]] + payment_method_type = "Interac" +[[paypal.credit]] + payment_method_type = "AmericanExpress" +[[paypal.credit]] + payment_method_type = "JCB" +[[paypal.credit]] + payment_method_type = "DinersClub" +[[paypal.credit]] + payment_method_type = "Discover" +[[paypal.credit]] + payment_method_type = "CartesBancaires" +[[paypal.credit]] + payment_method_type = "UnionPay" +[[paypal.debit]] + payment_method_type = "Mastercard" +[[paypal.debit]] + payment_method_type = "Visa" +[[paypal.debit]] + payment_method_type = "Interac" +[[paypal.debit]] + payment_method_type = "AmericanExpress" +[[paypal.debit]] + payment_method_type = "JCB" +[[paypal.debit]] + payment_method_type = "DinersClub" +[[paypal.debit]] + payment_method_type = "Discover" +[[paypal.debit]] + payment_method_type = "CartesBancaires" +[[paypal.debit]] + payment_method_type = "UnionPay" +[[paypal.wallet]] + payment_method_type = "paypal" +is_verifiable = true +[paypal.connector_auth.BodyKey] +api_key="Client Secret" +key1="Client ID" +[paypal.connector_webhook_details] +merchant_secret="Source verification key" + +[payu] +[[payu.credit]] + payment_method_type = "Mastercard" +[[payu.credit]] + payment_method_type = "Visa" +[[payu.credit]] + payment_method_type = "Interac" +[[payu.credit]] + payment_method_type = "AmericanExpress" +[[payu.credit]] + payment_method_type = "JCB" +[[payu.credit]] + payment_method_type = "DinersClub" +[[payu.credit]] + payment_method_type = "Discover" +[[payu.credit]] + payment_method_type = "CartesBancaires" +[[payu.credit]] + payment_method_type = "UnionPay" +[[payu.debit]] + payment_method_type = "Mastercard" +[[payu.debit]] + payment_method_type = "Visa" +[[payu.debit]] + payment_method_type = "Interac" +[[payu.debit]] + payment_method_type = "AmericanExpress" +[[payu.debit]] + payment_method_type = "JCB" +[[payu.debit]] + payment_method_type = "DinersClub" +[[payu.debit]] + payment_method_type = "Discover" +[[payu.debit]] + payment_method_type = "CartesBancaires" +[[payu.debit]] + payment_method_type = "UnionPay" +[[payu.wallet]] + payment_method_type = "google_pay" +[payu.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant POS ID" +[payu.connector_webhook_details] +merchant_secret="Source verification key" + +[payu.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[rapyd] +[[rapyd.credit]] + payment_method_type = "Mastercard" +[[rapyd.credit]] + payment_method_type = "Visa" +[[rapyd.credit]] + payment_method_type = "Interac" +[[rapyd.credit]] + payment_method_type = "AmericanExpress" +[[rapyd.credit]] + payment_method_type = "JCB" +[[rapyd.credit]] + payment_method_type = "DinersClub" +[[rapyd.credit]] + payment_method_type = "Discover" +[[rapyd.credit]] + payment_method_type = "CartesBancaires" +[[rapyd.credit]] + payment_method_type = "UnionPay" +[[rapyd.debit]] + payment_method_type = "Mastercard" +[[rapyd.debit]] + payment_method_type = "Visa" +[[rapyd.debit]] + payment_method_type = "Interac" +[[rapyd.debit]] + payment_method_type = "AmericanExpress" +[[rapyd.debit]] + payment_method_type = "JCB" +[[rapyd.debit]] + payment_method_type = "DinersClub" +[[rapyd.debit]] + payment_method_type = "Discover" +[[rapyd.debit]] + payment_method_type = "CartesBancaires" +[[rapyd.debit]] + payment_method_type = "UnionPay" +[[rapyd.wallet]] + payment_method_type = "apple_pay" +[rapyd.connector_auth.BodyKey] +api_key="Access Key" +key1="API Secret" +[rapyd.connector_webhook_details] +merchant_secret="Source verification key" + +[rapyd.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[rapyd.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[shift4] +[[shift4.credit]] + payment_method_type = "Mastercard" +[[shift4.credit]] + payment_method_type = "Visa" +[[shift4.credit]] + payment_method_type = "Interac" +[[shift4.credit]] + payment_method_type = "AmericanExpress" +[[shift4.credit]] + payment_method_type = "JCB" +[[shift4.credit]] + payment_method_type = "DinersClub" +[[shift4.credit]] + payment_method_type = "Discover" +[[shift4.credit]] + payment_method_type = "CartesBancaires" +[[shift4.credit]] + payment_method_type = "UnionPay" +[[shift4.debit]] + payment_method_type = "Mastercard" +[[shift4.debit]] + payment_method_type = "Visa" +[[shift4.debit]] + payment_method_type = "Interac" +[[shift4.debit]] + payment_method_type = "AmericanExpress" +[[shift4.debit]] + payment_method_type = "JCB" +[[shift4.debit]] + payment_method_type = "DinersClub" +[[shift4.debit]] + payment_method_type = "Discover" +[[shift4.debit]] + payment_method_type = "CartesBancaires" +[[shift4.debit]] + payment_method_type = "UnionPay" +[[shift4.bank_redirect]] + payment_method_type = "ideal" +[[shift4.bank_redirect]] + payment_method_type = "giropay" +[[shift4.bank_redirect]] + payment_method_type = "sofort" +[[shift4.bank_redirect]] + payment_method_type = "eps" +[shift4.connector_auth.HeaderKey] +api_key="API Key" +[shift4.connector_webhook_details] +merchant_secret="Source verification key" + +[stripe] +[[stripe.credit]] + payment_method_type = "Mastercard" +[[stripe.credit]] + payment_method_type = "Visa" +[[stripe.credit]] + payment_method_type = "Interac" +[[stripe.credit]] + payment_method_type = "AmericanExpress" +[[stripe.credit]] + payment_method_type = "JCB" +[[stripe.credit]] + payment_method_type = "DinersClub" +[[stripe.credit]] + payment_method_type = "Discover" +[[stripe.credit]] + payment_method_type = "CartesBancaires" +[[stripe.credit]] + payment_method_type = "UnionPay" +[[stripe.debit]] + payment_method_type = "Mastercard" +[[stripe.debit]] + payment_method_type = "Visa" +[[stripe.debit]] + payment_method_type = "Interac" +[[stripe.debit]] + payment_method_type = "AmericanExpress" +[[stripe.debit]] + payment_method_type = "JCB" +[[stripe.debit]] + payment_method_type = "DinersClub" +[[stripe.debit]] + payment_method_type = "Discover" +[[stripe.debit]] + payment_method_type = "CartesBancaires" +[[stripe.debit]] + payment_method_type = "UnionPay" +[[stripe.pay_later]] + payment_method_type = "klarna" +[[stripe.pay_later]] + payment_method_type = "affirm" +[[stripe.pay_later]] + payment_method_type = "afterpay_clearpay" +[[stripe.bank_redirect]] + payment_method_type = "ideal" +[[stripe.bank_redirect]] + payment_method_type = "giropay" +[[stripe.bank_redirect]] + payment_method_type = "sofort" +[[stripe.bank_redirect]] + payment_method_type = "eps" +[[stripe.bank_debit]] + payment_method_type = "ach" +[[stripe.bank_debit]] + payment_method_type = "becs" +[[stripe.bank_debit]] + payment_method_type = "sepa" +[[stripe.bank_transfer]] + payment_method_type = "ach" +[[stripe.bank_transfer]] + payment_method_type = "bacs" +[[stripe.bank_transfer]] + payment_method_type = "sepa" +[[stripe.wallet]] + payment_method_type = "apple_pay" +[[stripe.wallet]] + payment_method_type = "google_pay" +is_verifiable = true +[stripe.connector_auth.HeaderKey] +api_key="Secret Key" +[stripe.connector_webhook_details] +merchant_secret="Source verification key" +[stripe.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +stripe_publishable_key="Stripe Publishable Key" +merchant_id="Google Pay Merchant ID" +[stripe.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[stripe.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + + + + +[trustpay] +[[trustpay.credit]] + payment_method_type = "Mastercard" +[[trustpay.credit]] + payment_method_type = "Visa" +[[trustpay.credit]] + payment_method_type = "Interac" +[[trustpay.credit]] + payment_method_type = "AmericanExpress" +[[trustpay.credit]] + payment_method_type = "JCB" +[[trustpay.credit]] + payment_method_type = "DinersClub" +[[trustpay.credit]] + payment_method_type = "Discover" +[[trustpay.credit]] + payment_method_type = "CartesBancaires" +[[trustpay.credit]] + payment_method_type = "UnionPay" +[[trustpay.debit]] + payment_method_type = "Mastercard" +[[trustpay.debit]] + payment_method_type = "Visa" +[[trustpay.debit]] + payment_method_type = "Interac" +[[trustpay.debit]] + payment_method_type = "AmericanExpress" +[[trustpay.debit]] + payment_method_type = "JCB" +[[trustpay.debit]] + payment_method_type = "DinersClub" +[[trustpay.debit]] + payment_method_type = "Discover" +[[trustpay.debit]] + payment_method_type = "CartesBancaires" +[[trustpay.debit]] + payment_method_type = "UnionPay" +[[trustpay.bank_redirect]] + payment_method_type = "ideal" +[[trustpay.bank_redirect]] + payment_method_type = "giropay" +[[trustpay.bank_redirect]] + payment_method_type = "sofort" +[[trustpay.bank_redirect]] + payment_method_type = "eps" +[[trustpay.bank_redirect]] + payment_method_type = "blik" +[[trustpay.wallet]] + payment_method_type = "apple_pay" +[[trustpay.wallet]] + payment_method_type = "google_pay" +[trustpay.connector_auth.SignatureKey] +api_key="API Key" +key1="Project ID" +api_secret="Secret Key" +[trustpay.connector_webhook_details] +merchant_secret="Source verification key" +[trustpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[trustpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[worldline] +[[worldline.credit]] + payment_method_type = "Mastercard" +[[worldline.credit]] + payment_method_type = "Visa" +[[worldline.credit]] + payment_method_type = "Interac" +[[worldline.credit]] + payment_method_type = "AmericanExpress" +[[worldline.credit]] + payment_method_type = "JCB" +[[worldline.credit]] + payment_method_type = "DinersClub" +[[worldline.credit]] + payment_method_type = "Discover" +[[worldline.credit]] + payment_method_type = "CartesBancaires" +[[worldline.credit]] + payment_method_type = "UnionPay" +[[worldline.debit]] + payment_method_type = "Mastercard" +[[worldline.debit]] + payment_method_type = "Visa" +[[worldline.debit]] + payment_method_type = "Interac" +[[worldline.debit]] + payment_method_type = "AmericanExpress" +[[worldline.debit]] + payment_method_type = "JCB" +[[worldline.debit]] + payment_method_type = "DinersClub" +[[worldline.debit]] + payment_method_type = "Discover" +[[worldline.debit]] + payment_method_type = "CartesBancaires" +[[worldline.debit]] + payment_method_type = "UnionPay" +[[worldline.bank_redirect]] + payment_method_type = "ideal" +[[worldline.bank_redirect]] + payment_method_type = "giropay" +[worldline.connector_auth.SignatureKey] +api_key="API Key ID" +key1="Merchant ID" +api_secret="Secret API Key" +[worldline.connector_webhook_details] +merchant_secret="Source verification key" + +[worldpay] +[[worldpay.credit]] + payment_method_type = "Mastercard" +[[worldpay.credit]] + payment_method_type = "Visa" +[[worldpay.credit]] + payment_method_type = "Interac" +[[worldpay.credit]] + payment_method_type = "AmericanExpress" +[[worldpay.credit]] + payment_method_type = "JCB" +[[worldpay.credit]] + payment_method_type = "DinersClub" +[[worldpay.credit]] + payment_method_type = "Discover" +[[worldpay.credit]] + payment_method_type = "CartesBancaires" +[[worldpay.credit]] + payment_method_type = "UnionPay" +[[worldpay.debit]] + payment_method_type = "Mastercard" +[[worldpay.debit]] + payment_method_type = "Visa" +[[worldpay.debit]] + payment_method_type = "Interac" +[[worldpay.debit]] + payment_method_type = "AmericanExpress" +[[worldpay.debit]] + payment_method_type = "JCB" +[[worldpay.debit]] + payment_method_type = "DinersClub" +[[worldpay.debit]] + payment_method_type = "Discover" +[[worldpay.debit]] + payment_method_type = "CartesBancaires" +[[worldpay.debit]] + payment_method_type = "UnionPay" +[[worldpay.wallet]] + payment_method_type = "google_pay" +[[worldpay.wallet]] + payment_method_type = "apple_pay" +[worldpay.connector_auth.BodyKey] +api_key="Username" +key1="Password" +[worldpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" +[worldpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[worldpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" +[worldpay.connector_webhook_details] +merchant_secret="Source verification key" + + + +[payme] +[[payme.credit]] + payment_method_type = "Mastercard" +[[payme.credit]] + payment_method_type = "Visa" +[[payme.credit]] + payment_method_type = "Interac" +[[payme.credit]] + payment_method_type = "AmericanExpress" +[[payme.credit]] + payment_method_type = "JCB" +[[payme.credit]] + payment_method_type = "DinersClub" +[[payme.credit]] + payment_method_type = "Discover" +[[payme.credit]] + payment_method_type = "CartesBancaires" +[[payme.credit]] + payment_method_type = "UnionPay" +[[payme.debit]] + payment_method_type = "Mastercard" +[[payme.debit]] + payment_method_type = "Visa" +[[payme.debit]] + payment_method_type = "Interac" +[[payme.debit]] + payment_method_type = "AmericanExpress" +[[payme.debit]] + payment_method_type = "JCB" +[[payme.debit]] + payment_method_type = "DinersClub" +[[payme.debit]] + payment_method_type = "Discover" +[[payme.debit]] + payment_method_type = "CartesBancaires" +[[payme.debit]] + payment_method_type = "UnionPay" +[payme.connector_auth.BodyKey] +api_key="Seller Payme Id" +key1="Payme Public Key" +[payme.connector_webhook_details] +merchant_secret="Payme Client Secret" +additional_secret="Payme Client Key" + +[powertranz] +[[powertranz.credit]] + payment_method_type = "Mastercard" +[[powertranz.credit]] + payment_method_type = "Visa" +[[powertranz.credit]] + payment_method_type = "Interac" +[[powertranz.credit]] + payment_method_type = "AmericanExpress" +[[powertranz.credit]] + payment_method_type = "JCB" +[[powertranz.credit]] + payment_method_type = "DinersClub" +[[powertranz.credit]] + payment_method_type = "Discover" +[[powertranz.credit]] + payment_method_type = "CartesBancaires" +[[powertranz.credit]] + payment_method_type = "UnionPay" +[[powertranz.debit]] + payment_method_type = "Mastercard" +[[powertranz.debit]] + payment_method_type = "Visa" +[[powertranz.debit]] + payment_method_type = "Interac" +[[powertranz.debit]] + payment_method_type = "AmericanExpress" +[[powertranz.debit]] + payment_method_type = "JCB" +[[powertranz.debit]] + payment_method_type = "DinersClub" +[[powertranz.debit]] + payment_method_type = "Discover" +[[powertranz.debit]] + payment_method_type = "CartesBancaires" +[[powertranz.debit]] + payment_method_type = "UnionPay" +[powertranz.connector_auth.BodyKey] +key1 = "PowerTranz Id" +api_key="PowerTranz Password" +[powertranz.connector_webhook_details] +merchant_secret="Source verification key" + + + +[tsys] +[[tsys.credit]] + payment_method_type = "Mastercard" +[[tsys.credit]] + payment_method_type = "Visa" +[[tsys.credit]] + payment_method_type = "Interac" +[[tsys.credit]] + payment_method_type = "AmericanExpress" +[[tsys.credit]] + payment_method_type = "JCB" +[[tsys.credit]] + payment_method_type = "DinersClub" +[[tsys.credit]] + payment_method_type = "Discover" +[[tsys.credit]] + payment_method_type = "CartesBancaires" +[[tsys.credit]] + payment_method_type = "UnionPay" +[[tsys.debit]] + payment_method_type = "Mastercard" +[[tsys.debit]] + payment_method_type = "Visa" +[[tsys.debit]] + payment_method_type = "Interac" +[[tsys.debit]] + payment_method_type = "AmericanExpress" +[[tsys.debit]] + payment_method_type = "JCB" +[[tsys.debit]] + payment_method_type = "DinersClub" +[[tsys.debit]] + payment_method_type = "Discover" +[[tsys.debit]] + payment_method_type = "CartesBancaires" +[[tsys.debit]] + payment_method_type = "UnionPay" +[tsys.connector_auth.SignatureKey] +api_key="Device Id" +key1="Transaction Key" +api_secret="Developer Id" +[tsys.connector_webhook_details] +merchant_secret="Source verification key" + +[zen] +[[zen.credit]] + payment_method_type = "Mastercard" +[[zen.credit]] + payment_method_type = "Visa" +[[zen.credit]] + payment_method_type = "Interac" +[[zen.credit]] + payment_method_type = "AmericanExpress" +[[zen.credit]] + payment_method_type = "JCB" +[[zen.credit]] + payment_method_type = "DinersClub" +[[zen.credit]] + payment_method_type = "Discover" +[[zen.credit]] + payment_method_type = "CartesBancaires" +[[zen.credit]] + payment_method_type = "UnionPay" +[[zen.debit]] + payment_method_type = "Mastercard" +[[zen.debit]] + payment_method_type = "Visa" +[[zen.debit]] + payment_method_type = "Interac" +[[zen.debit]] + payment_method_type = "AmericanExpress" +[[zen.debit]] + payment_method_type = "JCB" +[[zen.debit]] + payment_method_type = "DinersClub" +[[zen.debit]] + payment_method_type = "Discover" +[[zen.debit]] + payment_method_type = "CartesBancaires" +[[zen.debit]] + payment_method_type = "UnionPay" +[[zen.wallet]] + payment_method_type = "apple_pay" +[[zen.wallet]] + payment_method_type = "google_pay" +[[zen.voucher]] + payment_method_type = "boleto" +[[zen.voucher]] + payment_method_type = "efecty" +[[zen.voucher]] + payment_method_type = "pago_efectivo" +[[zen.voucher]] + payment_method_type = "red_compra" +[[zen.voucher]] + payment_method_type = "red_pagos" +[[zen.bank_transfer]] + payment_method_type = "pix" +[[zen.bank_transfer]] + payment_method_type = "pse" +[zen.connector_auth.HeaderKey] +api_key="API Key" +[zen.connector_webhook_details] +merchant_secret="Source verification key" +[zen.metadata.apple_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" +[zen.metadata.google_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml new file mode 100644 index 00000000000..47de5cd5d5f --- /dev/null +++ b/crates/connector_configs/toml/sandbox.toml @@ -0,0 +1,2505 @@ +[aci] +[[aci.credit]] + payment_method_type = "Mastercard" +[[aci.credit]] + payment_method_type = "Visa" +[[aci.credit]] + payment_method_type = "Interac" +[[aci.credit]] + payment_method_type = "AmericanExpress" +[[aci.credit]] + payment_method_type = "JCB" +[[aci.credit]] + payment_method_type = "DinersClub" +[[aci.credit]] + payment_method_type = "Discover" +[[aci.credit]] + payment_method_type = "CartesBancaires" +[[aci.credit]] + payment_method_type = "UnionPay" +[[aci.debit]] + payment_method_type = "Mastercard" +[[aci.debit]] + payment_method_type = "Visa" +[[aci.debit]] + payment_method_type = "Interac" +[[aci.debit]] + payment_method_type = "AmericanExpress" +[[aci.debit]] + payment_method_type = "JCB" +[[aci.debit]] + payment_method_type = "DinersClub" +[[aci.debit]] + payment_method_type = "Discover" +[[aci.debit]] + payment_method_type = "CartesBancaires" +[[aci.debit]] + payment_method_type = "UnionPay" +[[aci.wallet]] + payment_method_type = "ali_pay" +[[aci.wallet]] + payment_method_type = "mb_way" +[[aci.bank_redirect]] + payment_method_type = "ideal" +[[aci.bank_redirect]] + payment_method_type = "giropay" +[[aci.bank_redirect]] + payment_method_type = "sofort" +[[aci.bank_redirect]] + payment_method_type = "eps" +[[aci.bank_redirect]] + payment_method_type = "przelewy24" +[[aci.bank_redirect]] + payment_method_type = "trustly" +[[aci.bank_redirect]] + payment_method_type = "interac" +[aci.connector_auth.BodyKey] +api_key="API Key" +key1="Entity ID" +[aci.connector_webhook_details] +merchant_secret="Source verification key" + + +[adyen] +[[adyen.credit]] + payment_method_type = "Mastercard" +[[adyen.credit]] + payment_method_type = "Visa" +[[adyen.credit]] + payment_method_type = "Interac" +[[adyen.credit]] + payment_method_type = "AmericanExpress" +[[adyen.credit]] + payment_method_type = "JCB" +[[adyen.credit]] + payment_method_type = "DinersClub" +[[adyen.credit]] + payment_method_type = "Discover" +[[adyen.credit]] + payment_method_type = "CartesBancaires" +[[adyen.credit]] + payment_method_type = "UnionPay" +[[adyen.debit]] + payment_method_type = "Mastercard" +[[adyen.debit]] + payment_method_type = "Visa" +[[adyen.debit]] + payment_method_type = "Interac" +[[adyen.debit]] + payment_method_type = "AmericanExpress" +[[adyen.debit]] + payment_method_type = "JCB" +[[adyen.debit]] + payment_method_type = "DinersClub" +[[adyen.debit]] + payment_method_type = "Discover" +[[adyen.debit]] + payment_method_type = "CartesBancaires" +[[adyen.debit]] + payment_method_type = "UnionPay" +[[adyen.pay_later]] + payment_method_type = "klarna" +[[adyen.pay_later]] + payment_method_type = "affirm" +[[adyen.pay_later]] + payment_method_type = "afterpay_clearpay" +[[adyen.pay_later]] + payment_method_type = "pay_bright" +[[adyen.pay_later]] + payment_method_type = "walley" +[[adyen.pay_later]] + payment_method_type = "alma" +[[adyen.pay_later]] + payment_method_type = "atome" +[[adyen.bank_debit]] + payment_method_type = "ach" +[[adyen.bank_debit]] + payment_method_type = "bacs" +[[adyen.bank_debit]] + payment_method_type = "sepa" +[[adyen.bank_redirect]] + payment_method_type = "ideal" +[[adyen.bank_redirect]] + payment_method_type = "giropay" +[[adyen.bank_redirect]] + payment_method_type = "sofort" +[[adyen.bank_redirect]] + payment_method_type = "eps" +[[adyen.bank_redirect]] + payment_method_type = "blik" +[[adyen.bank_redirect]] + payment_method_type = "przelewy24" +[[adyen.bank_redirect]] + payment_method_type = "trustly" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_czech_republic" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_finland" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_poland" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_slovakia" +[[adyen.bank_redirect]] + payment_method_type = "bancontact_card" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_fpx" +[[adyen.bank_redirect]] + payment_method_type = "online_banking_thailand" +[[adyen.bank_redirect]] + payment_method_type = "bizum" +[[adyen.bank_redirect]] + payment_method_type = "open_banking_uk" +[[adyen.bank_transfer]] + payment_method_type = "permata_bank_transfer" +[[adyen.bank_transfer]] + payment_method_type = "bca_bank_transfer" +[[adyen.bank_transfer]] + payment_method_type = "bni_va" +[[adyen.bank_transfer]] + payment_method_type = "bri_va" +[[adyen.bank_transfer]] + payment_method_type = "cimb_va" +[[adyen.bank_transfer]] + payment_method_type = "danamon_va" +[[adyen.bank_transfer]] + payment_method_type = "mandiri_va" +[[adyen.wallet]] + payment_method_type = "apple_pay" +[[adyen.wallet]] + payment_method_type = "google_pay" +[[adyen.wallet]] + payment_method_type = "paypal" +[[adyen.wallet]] + payment_method_type = "we_chat_pay" +[[adyen.wallet]] + payment_method_type = "ali_pay" +[[adyen.wallet]] + payment_method_type = "mb_way" +[[adyen.wallet]] + payment_method_type = "ali_pay_hk" +[[adyen.wallet]] + payment_method_type = "go_pay" +[[adyen.wallet]] + payment_method_type = "kakao_pay" +[[adyen.wallet]] + payment_method_type = "twint" +[[adyen.wallet]] + payment_method_type = "gcash" +[[adyen.wallet]] + payment_method_type = "vipps" +[[adyen.wallet]] + payment_method_type = "dana" +[[adyen.wallet]] + payment_method_type = "momo" +[[adyen.wallet]] + payment_method_type = "swish" +[[adyen.wallet]] + payment_method_type = "touch_n_go" +[[adyen.voucher]] + payment_method_type = "boleto" +[[adyen.voucher]] + payment_method_type = "alfamart" +[[adyen.voucher]] + payment_method_type = "indomaret" +[[adyen.voucher]] + payment_method_type = "oxxo" +[[adyen.voucher]] + payment_method_type = "seven_eleven" +[[adyen.voucher]] + payment_method_type = "lawson" +[[adyen.voucher]] + payment_method_type = "mini_stop" +[[adyen.voucher]] + payment_method_type = "family_mart" +[[adyen.voucher]] + payment_method_type = "seicomart" +[[adyen.voucher]] + payment_method_type = "pay_easy" +[[adyen.gift_card]] + payment_method_type = "pay_safe_card" +[[adyen.gift_card]] + payment_method_type = "givex" +[[adyen.card_redirect]] + payment_method_type = "benefit" +[[adyen.card_redirect]] + payment_method_type = "knet" +[[adyen.card_redirect]] + payment_method_type = "momo_atm" +[adyen.connector_auth.BodyKey] +api_key="Adyen API Key" +key1="Adyen Account Id" +[adyen.connector_webhook_details] +merchant_secret="Source verification key" +[adyen.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[adyen.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[adyen.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[airwallex] +[[airwallex.credit]] + payment_method_type = "Mastercard" +[[airwallex.credit]] + payment_method_type = "Visa" +[[airwallex.credit]] + payment_method_type = "Interac" +[[airwallex.credit]] + payment_method_type = "AmericanExpress" +[[airwallex.credit]] + payment_method_type = "JCB" +[[airwallex.credit]] + payment_method_type = "DinersClub" +[[airwallex.credit]] + payment_method_type = "Discover" +[[airwallex.credit]] + payment_method_type = "CartesBancaires" +[[airwallex.credit]] + payment_method_type = "UnionPay" +[[airwallex.debit]] + payment_method_type = "Mastercard" +[[airwallex.debit]] + payment_method_type = "Visa" +[[airwallex.debit]] + payment_method_type = "Interac" +[[airwallex.debit]] + payment_method_type = "AmericanExpress" +[[airwallex.debit]] + payment_method_type = "JCB" +[[airwallex.debit]] + payment_method_type = "DinersClub" +[[airwallex.debit]] + payment_method_type = "Discover" +[[airwallex.debit]] + payment_method_type = "CartesBancaires" +[[airwallex.debit]] + payment_method_type = "UnionPay" +[[airwallex.wallet]] + payment_method_type = "google_pay" +[airwallex.connector_auth.BodyKey] +api_key="API Key" +key1="Client ID" +[airwallex.connector_webhook_details] +merchant_secret="Source verification key" + + +[authorizedotnet] +[[authorizedotnet.credit]] + payment_method_type = "Mastercard" +[[authorizedotnet.credit]] + payment_method_type = "Visa" +[[authorizedotnet.credit]] + payment_method_type = "Interac" +[[authorizedotnet.credit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.credit]] + payment_method_type = "JCB" +[[authorizedotnet.credit]] + payment_method_type = "DinersClub" +[[authorizedotnet.credit]] + payment_method_type = "Discover" +[[authorizedotnet.credit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.credit]] + payment_method_type = "UnionPay" +[[authorizedotnet.debit]] + payment_method_type = "Mastercard" +[[authorizedotnet.debit]] + payment_method_type = "Visa" +[[authorizedotnet.debit]] + payment_method_type = "Interac" +[[authorizedotnet.debit]] + payment_method_type = "AmericanExpress" +[[authorizedotnet.debit]] + payment_method_type = "JCB" +[[authorizedotnet.debit]] + payment_method_type = "DinersClub" +[[authorizedotnet.debit]] + payment_method_type = "Discover" +[[authorizedotnet.debit]] + payment_method_type = "CartesBancaires" +[[authorizedotnet.debit]] + payment_method_type = "UnionPay" +[[authorizedotnet.wallet]] + payment_method_type = "google_pay" +[[authorizedotnet.wallet]] + payment_method_type = "paypal" +[authorizedotnet.connector_auth.BodyKey] +api_key="API Login ID" +key1="Transaction Key" +[authorizedotnet.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" +[authorizedotnet.connector_webhook_details] +merchant_secret="Source verification key" + +[bambora] +[[bambora.credit]] + payment_method_type = "Mastercard" +[[bambora.credit]] + payment_method_type = "Visa" +[[bambora.credit]] + payment_method_type = "Interac" +[[bambora.credit]] + payment_method_type = "AmericanExpress" +[[bambora.credit]] + payment_method_type = "JCB" +[[bambora.credit]] + payment_method_type = "DinersClub" +[[bambora.credit]] + payment_method_type = "Discover" +[[bambora.credit]] + payment_method_type = "CartesBancaires" +[[bambora.credit]] + payment_method_type = "UnionPay" +[[bambora.debit]] + payment_method_type = "Mastercard" +[[bambora.debit]] + payment_method_type = "Visa" +[[bambora.debit]] + payment_method_type = "Interac" +[[bambora.debit]] + payment_method_type = "AmericanExpress" +[[bambora.debit]] + payment_method_type = "JCB" +[[bambora.debit]] + payment_method_type = "DinersClub" +[[bambora.debit]] + payment_method_type = "Discover" +[[bambora.debit]] + payment_method_type = "CartesBancaires" +[[bambora.debit]] + payment_method_type = "UnionPay" +[[bambora.wallet]] + payment_method_type = "apple_pay" +[[bambora.wallet]] + payment_method_type = "paypal" +[bambora.connector_auth.BodyKey] +api_key="Passcode" +key1="Merchant Id" +[bambora.connector_webhook_details] +merchant_secret="Source verification key" +[bambora.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bambora.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica] +[[bankofamerica.credit]] + payment_method_type = "Mastercard" +[[bankofamerica.credit]] + payment_method_type = "Visa" +[[bankofamerica.credit]] + payment_method_type = "Interac" +[[bankofamerica.credit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.credit]] + payment_method_type = "JCB" +[[bankofamerica.credit]] + payment_method_type = "DinersClub" +[[bankofamerica.credit]] + payment_method_type = "Discover" +[[bankofamerica.credit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.credit]] + payment_method_type = "UnionPay" +[[bankofamerica.debit]] + payment_method_type = "Mastercard" +[[bankofamerica.debit]] + payment_method_type = "Visa" +[[bankofamerica.debit]] + payment_method_type = "Interac" +[[bankofamerica.debit]] + payment_method_type = "AmericanExpress" +[[bankofamerica.debit]] + payment_method_type = "JCB" +[[bankofamerica.debit]] + payment_method_type = "DinersClub" +[[bankofamerica.debit]] + payment_method_type = "Discover" +[[bankofamerica.debit]] + payment_method_type = "CartesBancaires" +[[bankofamerica.debit]] + payment_method_type = "UnionPay" +[[bankofamerica.wallet]] + payment_method_type = "apple_pay" +[[bankofamerica.wallet]] + payment_method_type = "google_pay" +[bankofamerica.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[bankofamerica.connector_webhook_details] +merchant_secret="Source verification key" + +[bankofamerica.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bankofamerica.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[bankofamerica.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[bitpay] +[[bitpay.crypto]] + payment_method_type = "crypto_currency" +[bitpay.connector_auth.HeaderKey] +api_key="API Key" +[bitpay.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap] +[[bluesnap.credit]] + payment_method_type = "Mastercard" +[[bluesnap.credit]] + payment_method_type = "Visa" +[[bluesnap.credit]] + payment_method_type = "Interac" +[[bluesnap.credit]] + payment_method_type = "AmericanExpress" +[[bluesnap.credit]] + payment_method_type = "JCB" +[[bluesnap.credit]] + payment_method_type = "DinersClub" +[[bluesnap.credit]] + payment_method_type = "Discover" +[[bluesnap.credit]] + payment_method_type = "CartesBancaires" +[[bluesnap.credit]] + payment_method_type = "UnionPay" +[[bluesnap.debit]] + payment_method_type = "Mastercard" +[[bluesnap.debit]] + payment_method_type = "Visa" +[[bluesnap.debit]] + payment_method_type = "Interac" +[[bluesnap.debit]] + payment_method_type = "AmericanExpress" +[[bluesnap.debit]] + payment_method_type = "JCB" +[[bluesnap.debit]] + payment_method_type = "DinersClub" +[[bluesnap.debit]] + payment_method_type = "Discover" +[[bluesnap.debit]] + payment_method_type = "CartesBancaires" +[[bluesnap.debit]] + payment_method_type = "UnionPay" +[[bluesnap.wallet]] + payment_method_type = "google_pay" +[[bluesnap.wallet]] + payment_method_type = "apple_pay" +[bluesnap.connector_auth.BodyKey] +api_key="Password" +key1="Username" +[bluesnap.connector_webhook_details] +merchant_secret="Source verification key" + +[bluesnap.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[bluesnap.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[bluesnap.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" +[bluesnap.metadata] +merchant_id="Merchant Id" + +[boku] +[[boku.wallet]] + payment_method_type = "dana" +[[boku.wallet]] + payment_method_type = "gcash" +[[boku.wallet]] + payment_method_type = "go_pay" +[[boku.wallet]] + payment_method_type = "kakao_pay" +[[boku.wallet]] + payment_method_type = "momo" +[boku.connector_auth.BodyKey] +api_key="API KEY" +key1= "MERCHANT ID" +[boku.connector_webhook_details] +merchant_secret="Source verification key" + + +[braintree] +[[braintree.credit]] + payment_method_type = "Mastercard" +[[braintree.credit]] + payment_method_type = "Visa" +[[braintree.credit]] + payment_method_type = "Interac" +[[braintree.credit]] + payment_method_type = "AmericanExpress" +[[braintree.credit]] + payment_method_type = "JCB" +[[braintree.credit]] + payment_method_type = "DinersClub" +[[braintree.credit]] + payment_method_type = "Discover" +[[braintree.credit]] + payment_method_type = "CartesBancaires" +[[braintree.credit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "Mastercard" +[[braintree.debit]] + payment_method_type = "Visa" +[[braintree.debit]] + payment_method_type = "Interac" +[[braintree.debit]] + payment_method_type = "AmericanExpress" +[[braintree.debit]] + payment_method_type = "JCB" +[[braintree.debit]] + payment_method_type = "DinersClub" +[[braintree.debit]] + payment_method_type = "Discover" +[[braintree.debit]] + payment_method_type = "CartesBancaires" +[[braintree.debit]] + payment_method_type = "UnionPay" +[[braintree.debit]] + payment_method_type = "UnionPay" +[braintree.connector_webhook_details] +merchant_secret="Source verification key" + + +[braintree.connector_auth.SignatureKey] +api_key="Public Key" +key1="Merchant Id" +api_secret="Private Key" +[braintree.metadata] +merchant_account_id="Merchant Account Id" +merchant_config_currency="Currency" + +[cashtocode] +[[cashtocode.reward]] + payment_method_type = "classic" +[[cashtocode.reward]] + payment_method_type = "evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.EUR.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.GBP.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.classic] +password_classic="Password Classic" +username_classic="Username Classic" +merchant_id_classic="MerchantId Classic" +[cashtocode.connector_auth.CurrencyAuthKey.auth_key_map.USD.evoucher] +password_evoucher="Password Evoucher" +username_evoucher="Username Evoucher" +merchant_id_evoucher="MerchantId Evoucher" +[cashtocode.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout] +[[checkout.credit]] + payment_method_type = "Mastercard" +[[checkout.credit]] + payment_method_type = "Visa" +[[checkout.credit]] + payment_method_type = "Interac" +[[checkout.credit]] + payment_method_type = "AmericanExpress" +[[checkout.credit]] + payment_method_type = "JCB" +[[checkout.credit]] + payment_method_type = "DinersClub" +[[checkout.credit]] + payment_method_type = "Discover" +[[checkout.credit]] + payment_method_type = "CartesBancaires" +[[checkout.credit]] + payment_method_type = "UnionPay" +[[checkout.debit]] + payment_method_type = "Mastercard" +[[checkout.debit]] + payment_method_type = "Visa" +[[checkout.debit]] + payment_method_type = "Interac" +[[checkout.debit]] + payment_method_type = "AmericanExpress" +[[checkout.debit]] + payment_method_type = "JCB" +[[checkout.debit]] + payment_method_type = "DinersClub" +[[checkout.debit]] + payment_method_type = "Discover" +[[checkout.debit]] + payment_method_type = "CartesBancaires" +[[checkout.debit]] + payment_method_type = "UnionPay" +[[checkout.wallet]] + payment_method_type = "apple_pay" +[[checkout.wallet]] + payment_method_type = "google_pay" +[[checkout.wallet]] + payment_method_type = "paypal" +[checkout.connector_auth.SignatureKey] +api_key="Checkout API Public Key" +key1="Processing Channel ID" +api_secret="Checkout API Secret Key" +[checkout.connector_webhook_details] +merchant_secret="Source verification key" + +[checkout.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[checkout.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[checkout.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[coinbase] +[[coinbase.crypto]] + payment_method_type = "crypto_currency" +[coinbase.connector_auth.HeaderKey] +api_key="API Key" +[coinbase.connector_webhook_details] +merchant_secret="Source verification key" + +[cryptopay] +[[cryptopay.crypto]] + payment_method_type = "crypto_currency" +[cryptopay.connector_auth.BodyKey] +api_key="API Key" +key1="Secret Key" +[cryptopay.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource] +[[cybersource.credit]] + payment_method_type = "Mastercard" +[[cybersource.credit]] + payment_method_type = "Visa" +[[cybersource.credit]] + payment_method_type = "Interac" +[[cybersource.credit]] + payment_method_type = "AmericanExpress" +[[cybersource.credit]] + payment_method_type = "JCB" +[[cybersource.credit]] + payment_method_type = "DinersClub" +[[cybersource.credit]] + payment_method_type = "Discover" +[[cybersource.credit]] + payment_method_type = "CartesBancaires" +[[cybersource.credit]] + payment_method_type = "UnionPay" +[[cybersource.debit]] + payment_method_type = "Mastercard" +[[cybersource.debit]] + payment_method_type = "Visa" +[[cybersource.debit]] + payment_method_type = "Interac" +[[cybersource.debit]] + payment_method_type = "AmericanExpress" +[[cybersource.debit]] + payment_method_type = "JCB" +[[cybersource.debit]] + payment_method_type = "DinersClub" +[[cybersource.debit]] + payment_method_type = "Discover" +[[cybersource.debit]] + payment_method_type = "CartesBancaires" +[[cybersource.debit]] + payment_method_type = "UnionPay" +[[cybersource.wallet]] + payment_method_type = "apple_pay" +[[cybersource.wallet]] + payment_method_type = "google_pay" +[cybersource.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" +[cybersource.connector_webhook_details] +merchant_secret="Source verification key" + +[cybersource.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[cybersource.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[cybersource.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[dlocal] +[[dlocal.credit]] + payment_method_type = "Mastercard" +[[dlocal.credit]] + payment_method_type = "Visa" +[[dlocal.credit]] + payment_method_type = "Interac" +[[dlocal.credit]] + payment_method_type = "AmericanExpress" +[[dlocal.credit]] + payment_method_type = "JCB" +[[dlocal.credit]] + payment_method_type = "DinersClub" +[[dlocal.credit]] + payment_method_type = "Discover" +[[dlocal.credit]] + payment_method_type = "CartesBancaires" +[[dlocal.credit]] + payment_method_type = "UnionPay" +[[dlocal.debit]] + payment_method_type = "Mastercard" +[[dlocal.debit]] + payment_method_type = "Visa" +[[dlocal.debit]] + payment_method_type = "Interac" +[[dlocal.debit]] + payment_method_type = "AmericanExpress" +[[dlocal.debit]] + payment_method_type = "JCB" +[[dlocal.debit]] + payment_method_type = "DinersClub" +[[dlocal.debit]] + payment_method_type = "Discover" +[[dlocal.debit]] + payment_method_type = "CartesBancaires" +[[dlocal.debit]] + payment_method_type = "UnionPay" +[dlocal.connector_auth.SignatureKey] +api_key="X Login" +key1="X Trans Key" +api_secret="Secret Key" +[dlocal.connector_webhook_details] +merchant_secret="Source verification key" + +[fiserv] +[[fiserv.credit]] + payment_method_type = "Mastercard" +[[fiserv.credit]] + payment_method_type = "Visa" +[[fiserv.credit]] + payment_method_type = "Interac" +[[fiserv.credit]] + payment_method_type = "AmericanExpress" +[[fiserv.credit]] + payment_method_type = "JCB" +[[fiserv.credit]] + payment_method_type = "DinersClub" +[[fiserv.credit]] + payment_method_type = "Discover" +[[fiserv.credit]] + payment_method_type = "CartesBancaires" +[[fiserv.credit]] + payment_method_type = "UnionPay" +[[fiserv.debit]] + payment_method_type = "Mastercard" +[[fiserv.debit]] + payment_method_type = "Visa" +[[fiserv.debit]] + payment_method_type = "Interac" +[[fiserv.debit]] + payment_method_type = "AmericanExpress" +[[fiserv.debit]] + payment_method_type = "JCB" +[[fiserv.debit]] + payment_method_type = "DinersClub" +[[fiserv.debit]] + payment_method_type = "Discover" +[[fiserv.debit]] + payment_method_type = "CartesBancaires" +[[fiserv.debit]] + payment_method_type = "UnionPay" +[fiserv.connector_auth.SignatureKey] +api_key="API Key" +key1="Merchant ID" +api_secret="API Secret" +[fiserv.metadata] +terminal_id="Terminal ID" +[fiserv.connector_webhook_details] +merchant_secret="Source verification key" + +[forte] +[[forte.credit]] + payment_method_type = "Mastercard" +[[forte.credit]] + payment_method_type = "Visa" +[[forte.credit]] + payment_method_type = "Interac" +[[forte.credit]] + payment_method_type = "AmericanExpress" +[[forte.credit]] + payment_method_type = "JCB" +[[forte.credit]] + payment_method_type = "DinersClub" +[[forte.credit]] + payment_method_type = "Discover" +[[forte.credit]] + payment_method_type = "CartesBancaires" +[[forte.credit]] + payment_method_type = "UnionPay" +[[forte.debit]] + payment_method_type = "Mastercard" +[[forte.debit]] + payment_method_type = "Visa" +[[forte.debit]] + payment_method_type = "Interac" +[[forte.debit]] + payment_method_type = "AmericanExpress" +[[forte.debit]] + payment_method_type = "JCB" +[[forte.debit]] + payment_method_type = "DinersClub" +[[forte.debit]] + payment_method_type = "Discover" +[[forte.debit]] + payment_method_type = "CartesBancaires" +[[forte.debit]] + payment_method_type = "UnionPay" +[forte.connector_auth.MultiAuthKey] +api_key="API Access ID" +key1="Organization ID" +api_secret="API Secure Key" +key2="Location ID" +[forte.connector_webhook_details] +merchant_secret="Source verification key" + +[globalpay] +[[globalpay.credit]] + payment_method_type = "Mastercard" +[[globalpay.credit]] + payment_method_type = "Visa" +[[globalpay.credit]] + payment_method_type = "Interac" +[[globalpay.credit]] + payment_method_type = "AmericanExpress" +[[globalpay.credit]] + payment_method_type = "JCB" +[[globalpay.credit]] + payment_method_type = "DinersClub" +[[globalpay.credit]] + payment_method_type = "Discover" +[[globalpay.credit]] + payment_method_type = "CartesBancaires" +[[globalpay.credit]] + payment_method_type = "UnionPay" +[[globalpay.debit]] + payment_method_type = "Mastercard" +[[globalpay.debit]] + payment_method_type = "Visa" +[[globalpay.debit]] + payment_method_type = "Interac" +[[globalpay.debit]] + payment_method_type = "AmericanExpress" +[[globalpay.debit]] + payment_method_type = "JCB" +[[globalpay.debit]] + payment_method_type = "DinersClub" +[[globalpay.debit]] + payment_method_type = "Discover" +[[globalpay.debit]] + payment_method_type = "CartesBancaires" +[[globalpay.debit]] + payment_method_type = "UnionPay" +[[globalpay.bank_redirect]] + payment_method_type = "ideal" +[[globalpay.bank_redirect]] + payment_method_type = "giropay" +[[globalpay.bank_redirect]] + payment_method_type = "sofort" +[[globalpay.bank_redirect]] + payment_method_type = "eps" +[[globalpay.wallet]] + payment_method_type = "google_pay" +[[globalpay.wallet]] + payment_method_type = "paypal" +[globalpay.connector_auth.BodyKey] +api_key="Global App Key" +key1="Global App ID" +[globalpay.metadata] +account_name="Account Name" +[globalpay.connector_webhook_details] +merchant_secret="Source verification key" +[globalpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[globepay] +[[globepay.wallet]] + payment_method_type = "we_chat_pay" +[[globepay.wallet]] + payment_method_type = "ali_pay" +[globepay.connector_auth.BodyKey] +api_key="Partner Code" +key1="Credential Code" +[globepay.connector_webhook_details] +merchant_secret="Source verification key" + +[gocardless] +[[gocardless.bank_debit]] + payment_method_type = "ach" +[[gocardless.bank_debit]] + payment_method_type = "becs" +[[gocardless.bank_debit]] + payment_method_type = "sepa" +[gocardless.connector_auth.HeaderKey] +api_key="Access Token" +[gocardless.connector_webhook_details] +merchant_secret="Source verification key" + +[iatapay] +[[iatapay.upi]] + payment_method_type = "upi_collect" +[iatapay.connector_auth.SignatureKey] +api_key="Client ID" +key1="Airline ID" +api_secret="Client Secret" +[iatapay.connector_webhook_details] +merchant_secret="Source verification key" + +[klarna] +[[klarna.pay_later]] + payment_method_type = "klarna" +[klarna.connector_auth.HeaderKey] +api_key="Klarna API Key" +[klarna.connector_webhook_details] +merchant_secret="Source verification key" + +[mollie] +[[mollie.credit]] + payment_method_type = "Mastercard" +[[mollie.credit]] + payment_method_type = "Visa" +[[mollie.credit]] + payment_method_type = "Interac" +[[mollie.credit]] + payment_method_type = "AmericanExpress" +[[mollie.credit]] + payment_method_type = "JCB" +[[mollie.credit]] + payment_method_type = "DinersClub" +[[mollie.credit]] + payment_method_type = "Discover" +[[mollie.credit]] + payment_method_type = "CartesBancaires" +[[mollie.credit]] + payment_method_type = "UnionPay" +[[mollie.debit]] + payment_method_type = "Mastercard" +[[mollie.debit]] + payment_method_type = "Visa" +[[mollie.debit]] + payment_method_type = "Interac" +[[mollie.debit]] + payment_method_type = "AmericanExpress" +[[mollie.debit]] + payment_method_type = "JCB" +[[mollie.debit]] + payment_method_type = "DinersClub" +[[mollie.debit]] + payment_method_type = "Discover" +[[mollie.debit]] + payment_method_type = "CartesBancaires" +[[mollie.debit]] + payment_method_type = "UnionPay" +[[mollie.bank_redirect]] + payment_method_type = "ideal" +[[mollie.bank_redirect]] + payment_method_type = "giropay" +[[mollie.bank_redirect]] + payment_method_type = "sofort" +[[mollie.bank_redirect]] + payment_method_type = "eps" +[[mollie.bank_redirect]] + payment_method_type = "przelewy24" +[[mollie.bank_redirect]] + payment_method_type = "bancontact_card" +[[mollie.wallet]] + payment_method_type = "paypal" +[mollie.connector_auth.BodyKey] +api_key="API Key" +key1="Profile Token" +[mollie.connector_webhook_details] +merchant_secret="Source verification key" + +[multisafepay] +[[multisafepay.credit]] + payment_method_type = "Mastercard" +[[multisafepay.credit]] + payment_method_type = "Visa" +[[multisafepay.credit]] + payment_method_type = "Interac" +[[multisafepay.credit]] + payment_method_type = "AmericanExpress" +[[multisafepay.credit]] + payment_method_type = "JCB" +[[multisafepay.credit]] + payment_method_type = "DinersClub" +[[multisafepay.credit]] + payment_method_type = "Discover" +[[multisafepay.credit]] + payment_method_type = "CartesBancaires" +[[multisafepay.credit]] + payment_method_type = "UnionPay" +[[multisafepay.debit]] + payment_method_type = "Mastercard" +[[multisafepay.debit]] + payment_method_type = "Visa" +[[multisafepay.debit]] + payment_method_type = "Interac" +[[multisafepay.debit]] + payment_method_type = "AmericanExpress" +[[multisafepay.debit]] + payment_method_type = "JCB" +[[multisafepay.debit]] + payment_method_type = "DinersClub" +[[multisafepay.debit]] + payment_method_type = "Discover" +[[multisafepay.debit]] + payment_method_type = "CartesBancaires" +[[multisafepay.debit]] + payment_method_type = "UnionPay" +[[multisafepay.wallet]] + payment_method_type = "google_pay" +[[multisafepay.wallet]] + payment_method_type = "paypal" +[multisafepay.connector_auth.HeaderKey] +api_key="Enter API Key" +[multisafepay.connector_webhook_details] +merchant_secret="Source verification key" +[multisafepay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nexinets] +[[nexinets.credit]] + payment_method_type = "Mastercard" +[[nexinets.credit]] + payment_method_type = "Visa" +[[nexinets.credit]] + payment_method_type = "Interac" +[[nexinets.credit]] + payment_method_type = "AmericanExpress" +[[nexinets.credit]] + payment_method_type = "JCB" +[[nexinets.credit]] + payment_method_type = "DinersClub" +[[nexinets.credit]] + payment_method_type = "Discover" +[[nexinets.credit]] + payment_method_type = "CartesBancaires" +[[nexinets.credit]] + payment_method_type = "UnionPay" +[[nexinets.debit]] + payment_method_type = "Mastercard" +[[nexinets.debit]] + payment_method_type = "Visa" +[[nexinets.debit]] + payment_method_type = "Interac" +[[nexinets.debit]] + payment_method_type = "AmericanExpress" +[[nexinets.debit]] + payment_method_type = "JCB" +[[nexinets.debit]] + payment_method_type = "DinersClub" +[[nexinets.debit]] + payment_method_type = "Discover" +[[nexinets.debit]] + payment_method_type = "CartesBancaires" +[[nexinets.debit]] + payment_method_type = "UnionPay" +[[nexinets.bank_redirect]] + payment_method_type = "ideal" +[[nexinets.bank_redirect]] + payment_method_type = "giropay" +[[nexinets.bank_redirect]] + payment_method_type = "sofort" +[[nexinets.bank_redirect]] + payment_method_type = "eps" +[[nexinets.wallet]] + payment_method_type = "apple_pay" +[[nexinets.wallet]] + payment_method_type = "paypal" +[nexinets.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant ID" +[nexinets.connector_webhook_details] +merchant_secret="Source verification key" +[nexinets.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nexinets.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[nmi] +[[nmi.credit]] + payment_method_type = "Mastercard" +[[nmi.credit]] + payment_method_type = "Visa" +[[nmi.credit]] + payment_method_type = "Interac" +[[nmi.credit]] + payment_method_type = "AmericanExpress" +[[nmi.credit]] + payment_method_type = "JCB" +[[nmi.credit]] + payment_method_type = "DinersClub" +[[nmi.credit]] + payment_method_type = "Discover" +[[nmi.credit]] + payment_method_type = "CartesBancaires" +[[nmi.credit]] + payment_method_type = "UnionPay" +[[nmi.debit]] + payment_method_type = "Mastercard" +[[nmi.debit]] + payment_method_type = "Visa" +[[nmi.debit]] + payment_method_type = "Interac" +[[nmi.debit]] + payment_method_type = "AmericanExpress" +[[nmi.debit]] + payment_method_type = "JCB" +[[nmi.debit]] + payment_method_type = "DinersClub" +[[nmi.debit]] + payment_method_type = "Discover" +[[nmi.debit]] + payment_method_type = "CartesBancaires" +[[nmi.debit]] + payment_method_type = "UnionPay" +[[nmi.bank_redirect]] + payment_method_type = "ideal" +[[nmi.wallet]] + payment_method_type = "apple_pay" +[[nmi.wallet]] + payment_method_type = "google_pay" +[nmi.connector_auth.BodyKey] +api_key="API Key" +key1="Public Key" +[nmi.connector_webhook_details] +merchant_secret="Source verification key" + +[nmi.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nmi.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nmi.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[noon] +[[noon.credit]] + payment_method_type = "Mastercard" +[[noon.credit]] + payment_method_type = "Visa" +[[noon.credit]] + payment_method_type = "Interac" +[[noon.credit]] + payment_method_type = "AmericanExpress" +[[noon.credit]] + payment_method_type = "JCB" +[[noon.credit]] + payment_method_type = "DinersClub" +[[noon.credit]] + payment_method_type = "Discover" +[[noon.credit]] + payment_method_type = "CartesBancaires" +[[noon.credit]] + payment_method_type = "UnionPay" +[[noon.debit]] + payment_method_type = "Mastercard" +[[noon.debit]] + payment_method_type = "Visa" +[[noon.debit]] + payment_method_type = "Interac" +[[noon.debit]] + payment_method_type = "AmericanExpress" +[[noon.debit]] + payment_method_type = "JCB" +[[noon.debit]] + payment_method_type = "DinersClub" +[[noon.debit]] + payment_method_type = "Discover" +[[noon.debit]] + payment_method_type = "CartesBancaires" +[[noon.debit]] + payment_method_type = "UnionPay" +[[noon.wallet]] + payment_method_type = "apple_pay" +[[noon.wallet]] + payment_method_type = "google_pay" +[[noon.wallet]] + payment_method_type = "paypal" +[noon.connector_auth.SignatureKey] +api_key="API Key" +key1="Business Identifier" +api_secret="Application Identifier" +[noon.connector_webhook_details] +merchant_secret="Source verification key" + +[noon.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[noon.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[noon.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[nuvei] +[[nuvei.credit]] + payment_method_type = "Mastercard" +[[nuvei.credit]] + payment_method_type = "Visa" +[[nuvei.credit]] + payment_method_type = "Interac" +[[nuvei.credit]] + payment_method_type = "AmericanExpress" +[[nuvei.credit]] + payment_method_type = "JCB" +[[nuvei.credit]] + payment_method_type = "DinersClub" +[[nuvei.credit]] + payment_method_type = "Discover" +[[nuvei.credit]] + payment_method_type = "CartesBancaires" +[[nuvei.credit]] + payment_method_type = "UnionPay" +[[nuvei.debit]] + payment_method_type = "Mastercard" +[[nuvei.debit]] + payment_method_type = "Visa" +[[nuvei.debit]] + payment_method_type = "Interac" +[[nuvei.debit]] + payment_method_type = "AmericanExpress" +[[nuvei.debit]] + payment_method_type = "JCB" +[[nuvei.debit]] + payment_method_type = "DinersClub" +[[nuvei.debit]] + payment_method_type = "Discover" +[[nuvei.debit]] + payment_method_type = "CartesBancaires" +[[nuvei.debit]] + payment_method_type = "UnionPay" +[[nuvei.pay_later]] + payment_method_type = "klarna" +[[nuvei.pay_later]] + payment_method_type = "afterpay_clearpay" +[[nuvei.bank_redirect]] + payment_method_type = "ideal" +[[nuvei.bank_redirect]] + payment_method_type = "giropay" +[[nuvei.bank_redirect]] + payment_method_type = "sofort" +[[nuvei.bank_redirect]] + payment_method_type = "eps" +[[nuvei.wallet]] + payment_method_type = "apple_pay" +[[nuvei.wallet]] + payment_method_type = "google_pay" +[[nuvei.wallet]] + payment_method_type = "paypal" +[nuvei.connector_auth.SignatureKey] +api_key="Merchant ID" +key1="Merchant Site ID" +api_secret="Merchant Secret" +[nuvei.connector_webhook_details] +merchant_secret="Source verification key" + +[nuvei.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[nuvei.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[nuvei.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + + +[opennode] +[[opennode.crypto]] + payment_method_type = "crypto_currency" +[opennode.connector_auth.HeaderKey] +api_key="API Key" +[opennode.connector_webhook_details] +merchant_secret="Source verification key" + +[prophetpay] +[[prophetpay.card_redirect]] + payment_method_type = "card_redirect" +[prophetpay.connector_auth.SignatureKey] +api_key="Username" +key1="Token" +api_secret="Profile" + +[payme] +[[payme.credit]] + payment_method_type = "Mastercard" +[[payme.credit]] + payment_method_type = "Visa" +[[payme.credit]] + payment_method_type = "Interac" +[[payme.credit]] + payment_method_type = "AmericanExpress" +[[payme.credit]] + payment_method_type = "JCB" +[[payme.credit]] + payment_method_type = "DinersClub" +[[payme.credit]] + payment_method_type = "Discover" +[[payme.credit]] + payment_method_type = "CartesBancaires" +[[payme.credit]] + payment_method_type = "UnionPay" +[[payme.debit]] + payment_method_type = "Mastercard" +[[payme.debit]] + payment_method_type = "Visa" +[[payme.debit]] + payment_method_type = "Interac" +[[payme.debit]] + payment_method_type = "AmericanExpress" +[[payme.debit]] + payment_method_type = "JCB" +[[payme.debit]] + payment_method_type = "DinersClub" +[[payme.debit]] + payment_method_type = "Discover" +[[payme.debit]] + payment_method_type = "CartesBancaires" +[[payme.debit]] + payment_method_type = "UnionPay" +[payme.connector_auth.BodyKey] +api_key="Seller Payme Id" +key1="Payme Public Key" +[payme.connector_webhook_details] +merchant_secret="Payme Client Secret" +additional_secret="Payme Client Key" + +[paypal] +[[paypal.credit]] + payment_method_type = "Mastercard" +[[paypal.credit]] + payment_method_type = "Visa" +[[paypal.credit]] + payment_method_type = "Interac" +[[paypal.credit]] + payment_method_type = "AmericanExpress" +[[paypal.credit]] + payment_method_type = "JCB" +[[paypal.credit]] + payment_method_type = "DinersClub" +[[paypal.credit]] + payment_method_type = "Discover" +[[paypal.credit]] + payment_method_type = "CartesBancaires" +[[paypal.credit]] + payment_method_type = "UnionPay" +[[paypal.debit]] + payment_method_type = "Mastercard" +[[paypal.debit]] + payment_method_type = "Visa" +[[paypal.debit]] + payment_method_type = "Interac" +[[paypal.debit]] + payment_method_type = "AmericanExpress" +[[paypal.debit]] + payment_method_type = "JCB" +[[paypal.debit]] + payment_method_type = "DinersClub" +[[paypal.debit]] + payment_method_type = "Discover" +[[paypal.debit]] + payment_method_type = "CartesBancaires" +[[paypal.debit]] + payment_method_type = "UnionPay" +[[paypal.wallet]] + payment_method_type = "paypal" +[[paypal.bank_redirect]] + payment_method_type = "ideal" +[[paypal.bank_redirect]] + payment_method_type = "giropay" +[[paypal.bank_redirect]] + payment_method_type = "sofort" +[[paypal.bank_redirect]] + payment_method_type = "eps" +is_verifiable = true +[paypal.connector_auth.BodyKey] +api_key="Client Secret" +key1="Client ID" +[paypal.connector_webhook_details] +merchant_secret="Source verification key" + + +[payu] +[[payu.credit]] + payment_method_type = "Mastercard" +[[payu.credit]] + payment_method_type = "Visa" +[[payu.credit]] + payment_method_type = "Interac" +[[payu.credit]] + payment_method_type = "AmericanExpress" +[[payu.credit]] + payment_method_type = "JCB" +[[payu.credit]] + payment_method_type = "DinersClub" +[[payu.credit]] + payment_method_type = "Discover" +[[payu.credit]] + payment_method_type = "CartesBancaires" +[[payu.credit]] + payment_method_type = "UnionPay" +[[payu.debit]] + payment_method_type = "Mastercard" +[[payu.debit]] + payment_method_type = "Visa" +[[payu.debit]] + payment_method_type = "Interac" +[[payu.debit]] + payment_method_type = "AmericanExpress" +[[payu.debit]] + payment_method_type = "JCB" +[[payu.debit]] + payment_method_type = "DinersClub" +[[payu.debit]] + payment_method_type = "Discover" +[[payu.debit]] + payment_method_type = "CartesBancaires" +[[payu.debit]] + payment_method_type = "UnionPay" +[[payu.wallet]] + payment_method_type = "google_pay" +[payu.connector_auth.BodyKey] +api_key="API Key" +key1="Merchant POS ID" +[payu.connector_webhook_details] +merchant_secret="Source verification key" + +[payu.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[placetopay] +[[placetopay.credit]] + payment_method_type = "Mastercard" +[[placetopay.credit]] + payment_method_type = "Visa" +[[placetopay.credit]] + payment_method_type = "Interac" +[[placetopay.credit]] + payment_method_type = "AmericanExpress" +[[placetopay.credit]] + payment_method_type = "JCB" +[[placetopay.credit]] + payment_method_type = "DinersClub" +[[placetopay.credit]] + payment_method_type = "Discover" +[[placetopay.credit]] + payment_method_type = "CartesBancaires" +[[placetopay.credit]] + payment_method_type = "UnionPay" +[[placetopay.debit]] + payment_method_type = "Mastercard" +[[placetopay.debit]] + payment_method_type = "Visa" +[[placetopay.debit]] + payment_method_type = "Interac" +[[placetopay.debit]] + payment_method_type = "AmericanExpress" +[[placetopay.debit]] + payment_method_type = "JCB" +[[placetopay.debit]] + payment_method_type = "DinersClub" +[[placetopay.debit]] + payment_method_type = "Discover" +[[placetopay.debit]] + payment_method_type = "CartesBancaires" +[[placetopay.debit]] + payment_method_type = "UnionPay" +[placetopay.connector_auth.BodyKey] +api_key="Login" +key1="Trankey" + +[powertranz] +[[powertranz.credit]] + payment_method_type = "Mastercard" +[[powertranz.credit]] + payment_method_type = "Visa" +[[powertranz.credit]] + payment_method_type = "Interac" +[[powertranz.credit]] + payment_method_type = "AmericanExpress" +[[powertranz.credit]] + payment_method_type = "JCB" +[[powertranz.credit]] + payment_method_type = "DinersClub" +[[powertranz.credit]] + payment_method_type = "Discover" +[[powertranz.credit]] + payment_method_type = "CartesBancaires" +[[powertranz.credit]] + payment_method_type = "UnionPay" +[[powertranz.debit]] + payment_method_type = "Mastercard" +[[powertranz.debit]] + payment_method_type = "Visa" +[[powertranz.debit]] + payment_method_type = "Interac" +[[powertranz.debit]] + payment_method_type = "AmericanExpress" +[[powertranz.debit]] + payment_method_type = "JCB" +[[powertranz.debit]] + payment_method_type = "DinersClub" +[[powertranz.debit]] + payment_method_type = "Discover" +[[powertranz.debit]] + payment_method_type = "CartesBancaires" +[[powertranz.debit]] + payment_method_type = "UnionPay" +[powertranz.connector_auth.BodyKey] +key1 = "PowerTranz Id" +api_key="PowerTranz Password" +[powertranz.connector_webhook_details] +merchant_secret="Source verification key" + +[rapyd] +[[rapyd.credit]] + payment_method_type = "Mastercard" +[[rapyd.credit]] + payment_method_type = "Visa" +[[rapyd.credit]] + payment_method_type = "Interac" +[[rapyd.credit]] + payment_method_type = "AmericanExpress" +[[rapyd.credit]] + payment_method_type = "JCB" +[[rapyd.credit]] + payment_method_type = "DinersClub" +[[rapyd.credit]] + payment_method_type = "Discover" +[[rapyd.credit]] + payment_method_type = "CartesBancaires" +[[rapyd.credit]] + payment_method_type = "UnionPay" +[[rapyd.debit]] + payment_method_type = "Mastercard" +[[rapyd.debit]] + payment_method_type = "Visa" +[[rapyd.debit]] + payment_method_type = "Interac" +[[rapyd.debit]] + payment_method_type = "AmericanExpress" +[[rapyd.debit]] + payment_method_type = "JCB" +[[rapyd.debit]] + payment_method_type = "DinersClub" +[[rapyd.debit]] + payment_method_type = "Discover" +[[rapyd.debit]] + payment_method_type = "CartesBancaires" +[[rapyd.debit]] + payment_method_type = "UnionPay" +[[rapyd.wallet]] + payment_method_type = "apple_pay" +[rapyd.connector_auth.BodyKey] +api_key="Access Key" +key1="API Secret" +[rapyd.connector_webhook_details] +merchant_secret="Source verification key" + +[rapyd.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[rapyd.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[shift4] +[[shift4.credit]] + payment_method_type = "Mastercard" +[[shift4.credit]] + payment_method_type = "Visa" +[[shift4.credit]] + payment_method_type = "Interac" +[[shift4.credit]] + payment_method_type = "AmericanExpress" +[[shift4.credit]] + payment_method_type = "JCB" +[[shift4.credit]] + payment_method_type = "DinersClub" +[[shift4.credit]] + payment_method_type = "Discover" +[[shift4.credit]] + payment_method_type = "CartesBancaires" +[[shift4.credit]] + payment_method_type = "UnionPay" +[[shift4.debit]] + payment_method_type = "Mastercard" +[[shift4.debit]] + payment_method_type = "Visa" +[[shift4.debit]] + payment_method_type = "Interac" +[[shift4.debit]] + payment_method_type = "AmericanExpress" +[[shift4.debit]] + payment_method_type = "JCB" +[[shift4.debit]] + payment_method_type = "DinersClub" +[[shift4.debit]] + payment_method_type = "Discover" +[[shift4.debit]] + payment_method_type = "CartesBancaires" +[[shift4.debit]] + payment_method_type = "UnionPay" +[[shift4.bank_redirect]] + payment_method_type = "ideal" +[[shift4.bank_redirect]] + payment_method_type = "giropay" +[[shift4.bank_redirect]] + payment_method_type = "sofort" +[[shift4.bank_redirect]] + payment_method_type = "eps" +[shift4.connector_auth.HeaderKey] +api_key="API Key" +[shift4.connector_webhook_details] +merchant_secret="Source verification key" + +[stripe] +[[stripe.credit]] + payment_method_type = "Mastercard" +[[stripe.credit]] + payment_method_type = "Visa" +[[stripe.credit]] + payment_method_type = "Interac" +[[stripe.credit]] + payment_method_type = "AmericanExpress" +[[stripe.credit]] + payment_method_type = "JCB" +[[stripe.credit]] + payment_method_type = "DinersClub" +[[stripe.credit]] + payment_method_type = "Discover" +[[stripe.credit]] + payment_method_type = "CartesBancaires" +[[stripe.credit]] + payment_method_type = "UnionPay" +[[stripe.debit]] + payment_method_type = "Mastercard" +[[stripe.debit]] + payment_method_type = "Visa" +[[stripe.debit]] + payment_method_type = "Interac" +[[stripe.debit]] + payment_method_type = "AmericanExpress" +[[stripe.debit]] + payment_method_type = "JCB" +[[stripe.debit]] + payment_method_type = "DinersClub" +[[stripe.debit]] + payment_method_type = "Discover" +[[stripe.debit]] + payment_method_type = "CartesBancaires" +[[stripe.debit]] + payment_method_type = "UnionPay" +[[stripe.pay_later]] + payment_method_type = "klarna" +[[stripe.pay_later]] + payment_method_type = "affirm" +[[stripe.pay_later]] + payment_method_type = "afterpay_clearpay" +[[stripe.bank_redirect]] + payment_method_type = "ideal" +[[stripe.bank_redirect]] + payment_method_type = "giropay" +[[stripe.bank_redirect]] + payment_method_type = "sofort" +[[stripe.bank_redirect]] + payment_method_type = "eps" +[[stripe.bank_redirect]] + payment_method_type = "bancontact_card" +[[stripe.bank_redirect]] + payment_method_type = "przelewy24" +[[stripe.bank_debit]] + payment_method_type = "ach" +[[stripe.bank_debit]] + payment_method_type = "bacs" +[[stripe.bank_debit]] + payment_method_type = "becs" +[[stripe.bank_debit]] + payment_method_type = "sepa" +[[stripe.bank_transfer]] + payment_method_type = "ach" +[[stripe.bank_transfer]] + payment_method_type = "bacs" +[[stripe.bank_transfer]] + payment_method_type = "sepa" +[[stripe.bank_transfer]] + payment_method_type = "multibanco" +[[stripe.wallet]] + payment_method_type = "apple_pay" +[[stripe.wallet]] + payment_method_type = "google_pay" +[[stripe.wallet]] + payment_method_type = "we_chat_pay" +[[stripe.wallet]] + payment_method_type = "ali_pay" +[[stripe.wallet]] + payment_method_type = "cashapp" +is_verifiable = true +[stripe.connector_auth.HeaderKey] +api_key="Secret Key" +[stripe.connector_webhook_details] +merchant_secret="Source verification key" + +[stripe.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +stripe_publishable_key="Stripe Publishable Key" +merchant_id="Google Pay Merchant ID" + +[stripe.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[stripe.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[stax] +[[stax.credit]] + payment_method_type = "Mastercard" +[[stax.credit]] + payment_method_type = "Visa" +[[stax.credit]] + payment_method_type = "Interac" +[[stax.credit]] + payment_method_type = "AmericanExpress" +[[stax.credit]] + payment_method_type = "JCB" +[[stax.credit]] + payment_method_type = "DinersClub" +[[stax.credit]] + payment_method_type = "Discover" +[[stax.credit]] + payment_method_type = "CartesBancaires" +[[stax.credit]] + payment_method_type = "UnionPay" +[[stax.debit]] + payment_method_type = "Mastercard" +[[stax.debit]] + payment_method_type = "Visa" +[[stax.debit]] + payment_method_type = "Interac" +[[stax.debit]] + payment_method_type = "AmericanExpress" +[[stax.debit]] + payment_method_type = "JCB" +[[stax.debit]] + payment_method_type = "DinersClub" +[[stax.debit]] + payment_method_type = "Discover" +[[stax.debit]] + payment_method_type = "CartesBancaires" +[[stax.debit]] + payment_method_type = "UnionPay" +[[stax.bank_debit]] + payment_method_type = "ach" +[stax.connector_auth.HeaderKey] +api_key="Api Key" +[stax.connector_webhook_details] +merchant_secret="Source verification key" + +[square] +[[square.credit]] + payment_method_type = "Mastercard" +[[square.credit]] + payment_method_type = "Visa" +[[square.credit]] + payment_method_type = "Interac" +[[square.credit]] + payment_method_type = "AmericanExpress" +[[square.credit]] + payment_method_type = "JCB" +[[square.credit]] + payment_method_type = "DinersClub" +[[square.credit]] + payment_method_type = "Discover" +[[square.credit]] + payment_method_type = "CartesBancaires" +[[square.credit]] + payment_method_type = "UnionPay" +[[square.debit]] + payment_method_type = "Mastercard" +[[square.debit]] + payment_method_type = "Visa" +[[square.debit]] + payment_method_type = "Interac" +[[square.debit]] + payment_method_type = "AmericanExpress" +[[square.debit]] + payment_method_type = "JCB" +[[square.debit]] + payment_method_type = "DinersClub" +[[square.debit]] + payment_method_type = "Discover" +[[square.debit]] + payment_method_type = "CartesBancaires" +[[square.debit]] + payment_method_type = "UnionPay" +[square_payout.connector_auth.BodyKey] +api_key = "Square API Key" +key1 = "Square Client Id" +[square.connector_webhook_details] +merchant_secret="Source verification key" + +[trustpay] +[[trustpay.credit]] + payment_method_type = "Mastercard" +[[trustpay.credit]] + payment_method_type = "Visa" +[[trustpay.credit]] + payment_method_type = "Interac" +[[trustpay.credit]] + payment_method_type = "AmericanExpress" +[[trustpay.credit]] + payment_method_type = "JCB" +[[trustpay.credit]] + payment_method_type = "DinersClub" +[[trustpay.credit]] + payment_method_type = "Discover" +[[trustpay.credit]] + payment_method_type = "CartesBancaires" +[[trustpay.credit]] + payment_method_type = "UnionPay" +[[trustpay.debit]] + payment_method_type = "Mastercard" +[[trustpay.debit]] + payment_method_type = "Visa" +[[trustpay.debit]] + payment_method_type = "Interac" +[[trustpay.debit]] + payment_method_type = "AmericanExpress" +[[trustpay.debit]] + payment_method_type = "JCB" +[[trustpay.debit]] + payment_method_type = "DinersClub" +[[trustpay.debit]] + payment_method_type = "Discover" +[[trustpay.debit]] + payment_method_type = "CartesBancaires" +[[trustpay.debit]] + payment_method_type = "UnionPay" +[[trustpay.bank_redirect]] + payment_method_type = "ideal" +[[trustpay.bank_redirect]] + payment_method_type = "giropay" +[[trustpay.bank_redirect]] + payment_method_type = "sofort" +[[trustpay.bank_redirect]] + payment_method_type = "eps" +[[trustpay.bank_redirect]] + payment_method_type = "blik" +[[trustpay.wallet]] + payment_method_type = "apple_pay" +[[trustpay.wallet]] + payment_method_type = "google_pay" +[trustpay.connector_auth.SignatureKey] +api_key="API Key" +key1="Project ID" +api_secret="Secret Key" +[trustpay.connector_webhook_details] +merchant_secret="Source verification key" + +[trustpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[trustpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[tsys] +[[tsys.credit]] + payment_method_type = "Mastercard" +[[tsys.credit]] + payment_method_type = "Visa" +[[tsys.credit]] + payment_method_type = "Interac" +[[tsys.credit]] + payment_method_type = "AmericanExpress" +[[tsys.credit]] + payment_method_type = "JCB" +[[tsys.credit]] + payment_method_type = "DinersClub" +[[tsys.credit]] + payment_method_type = "Discover" +[[tsys.credit]] + payment_method_type = "CartesBancaires" +[[tsys.credit]] + payment_method_type = "UnionPay" +[[tsys.debit]] + payment_method_type = "Mastercard" +[[tsys.debit]] + payment_method_type = "Visa" +[[tsys.debit]] + payment_method_type = "Interac" +[[tsys.debit]] + payment_method_type = "AmericanExpress" +[[tsys.debit]] + payment_method_type = "JCB" +[[tsys.debit]] + payment_method_type = "DinersClub" +[[tsys.debit]] + payment_method_type = "Discover" +[[tsys.debit]] + payment_method_type = "CartesBancaires" +[[tsys.debit]] + payment_method_type = "UnionPay" +[tsys.connector_auth.SignatureKey] +api_key="Device Id" +key1="Transaction Key" +api_secret="Developer Id" +[tsys.connector_webhook_details] +merchant_secret="Source verification key" + +[volt] +[[volt.bank_redirect]] + payment_method_type = "open_banking_uk" +[volt.connector_auth.MultiAuthKey] +api_key = "Username" +api_secret = "Password" +key1 = "Client ID" +key2 = "Client Secret" +[volt.connector_webhook_details] +merchant_secret="Source verification key" + +[worldline] +[[worldline.credit]] + payment_method_type = "Mastercard" +[[worldline.credit]] + payment_method_type = "Visa" +[[worldline.credit]] + payment_method_type = "Interac" +[[worldline.credit]] + payment_method_type = "AmericanExpress" +[[worldline.credit]] + payment_method_type = "JCB" +[[worldline.credit]] + payment_method_type = "DinersClub" +[[worldline.credit]] + payment_method_type = "Discover" +[[worldline.credit]] + payment_method_type = "CartesBancaires" +[[worldline.credit]] + payment_method_type = "UnionPay" +[[worldline.debit]] + payment_method_type = "Mastercard" +[[worldline.debit]] + payment_method_type = "Visa" +[[worldline.debit]] + payment_method_type = "Interac" +[[worldline.debit]] + payment_method_type = "AmericanExpress" +[[worldline.debit]] + payment_method_type = "JCB" +[[worldline.debit]] + payment_method_type = "DinersClub" +[[worldline.debit]] + payment_method_type = "Discover" +[[worldline.debit]] + payment_method_type = "CartesBancaires" +[[worldline.debit]] + payment_method_type = "UnionPay" +[[worldline.bank_redirect]] + payment_method_type = "ideal" +[[worldline.bank_redirect]] + payment_method_type = "giropay" +[worldline.connector_auth.SignatureKey] +api_key="API Key ID" +key1="Merchant ID" +api_secret="Secret API Key" +[worldline.connector_webhook_details] +merchant_secret="Source verification key" + +[worldpay] +[[worldpay.credit]] + payment_method_type = "Mastercard" +[[worldpay.credit]] + payment_method_type = "Visa" +[[worldpay.credit]] + payment_method_type = "Interac" +[[worldpay.credit]] + payment_method_type = "AmericanExpress" +[[worldpay.credit]] + payment_method_type = "JCB" +[[worldpay.credit]] + payment_method_type = "DinersClub" +[[worldpay.credit]] + payment_method_type = "Discover" +[[worldpay.credit]] + payment_method_type = "CartesBancaires" +[[worldpay.credit]] + payment_method_type = "UnionPay" +[[worldpay.debit]] + payment_method_type = "Mastercard" +[[worldpay.debit]] + payment_method_type = "Visa" +[[worldpay.debit]] + payment_method_type = "Interac" +[[worldpay.debit]] + payment_method_type = "AmericanExpress" +[[worldpay.debit]] + payment_method_type = "JCB" +[[worldpay.debit]] + payment_method_type = "DinersClub" +[[worldpay.debit]] + payment_method_type = "Discover" +[[worldpay.debit]] + payment_method_type = "CartesBancaires" +[[worldpay.debit]] + payment_method_type = "UnionPay" +[[worldpay.wallet]] + payment_method_type = "google_pay" +[[worldpay.wallet]] + payment_method_type = "apple_pay" +[worldpay.connector_auth.BodyKey] +api_key="Username" +key1="Password" +[worldpay.connector_webhook_details] +merchant_secret="Source verification key" + +[worldpay.metadata.google_pay] +merchant_name="Google Pay Merchant Name" +gateway_merchant_id="Google Pay Merchant Key" +merchant_id="Google Pay Merchant ID" + +[worldpay.metadata.apple_pay.session_token_data] +certificate="Merchant Certificate (Base64 Encoded)" +certificate_keys="Merchant PrivateKey (Base64 Encoded)" +merchant_identifier="Apple Merchant Identifier" +display_name="Display Name" +initiative="Domain" +initiative_context="Domain Name" +[worldpay.metadata.apple_pay.payment_request_data] +supported_networks=["visa","masterCard","amex","discover"] +merchant_capabilities=["supports3DS"] +label="apple" + +[zen] +[[zen.credit]] + payment_method_type = "Mastercard" +[[zen.credit]] + payment_method_type = "Visa" +[[zen.credit]] + payment_method_type = "Interac" +[[zen.credit]] + payment_method_type = "AmericanExpress" +[[zen.credit]] + payment_method_type = "JCB" +[[zen.credit]] + payment_method_type = "DinersClub" +[[zen.credit]] + payment_method_type = "Discover" +[[zen.credit]] + payment_method_type = "CartesBancaires" +[[zen.credit]] + payment_method_type = "UnionPay" +[[zen.debit]] + payment_method_type = "Mastercard" +[[zen.debit]] + payment_method_type = "Visa" +[[zen.debit]] + payment_method_type = "Interac" +[[zen.debit]] + payment_method_type = "AmericanExpress" +[[zen.debit]] + payment_method_type = "JCB" +[[zen.debit]] + payment_method_type = "DinersClub" +[[zen.debit]] + payment_method_type = "Discover" +[[zen.debit]] + payment_method_type = "CartesBancaires" +[[zen.debit]] + payment_method_type = "UnionPay" +[[zen.voucher]] + payment_method_type = "boleto" +[[zen.voucher]] + payment_method_type = "efecty" +[[zen.voucher]] + payment_method_type = "pago_efectivo" +[[zen.voucher]] + payment_method_type = "red_compra" +[[zen.voucher]] + payment_method_type = "red_pagos" +[[zen.bank_transfer]] + payment_method_type = "pix" +[[zen.bank_transfer]] + payment_method_type = "pse" +[[zen.wallet]] + payment_method_type = "apple_pay" +[[zen.wallet]] + payment_method_type = "google_pay" +[zen.connector_auth.HeaderKey] +api_key="API Key" +[zen.connector_webhook_details] +merchant_secret="Source verification key" + +[zen.metadata.apple_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" +[zen.metadata.google_pay] +terminal_uuid="Terminal UUID" +pay_wall_secret="Pay Wall Secret" + + +[dummy_connector] +[[dummy_connector.credit]] + payment_method_type = "Mastercard" +[[dummy_connector.credit]] + payment_method_type = "Visa" +[[dummy_connector.credit]] + payment_method_type = "Interac" +[[dummy_connector.credit]] + payment_method_type = "AmericanExpress" +[[dummy_connector.credit]] + payment_method_type = "JCB" +[[dummy_connector.credit]] + payment_method_type = "DinersClub" +[[dummy_connector.credit]] + payment_method_type = "Discover" +[[dummy_connector.credit]] + payment_method_type = "CartesBancaires" +[[dummy_connector.credit]] + payment_method_type = "UnionPay" +[[dummy_connector.debit]] + payment_method_type = "Mastercard" +[[dummy_connector.debit]] + payment_method_type = "Visa" +[[dummy_connector.debit]] + payment_method_type = "Interac" +[[dummy_connector.debit]] + payment_method_type = "AmericanExpress" +[[dummy_connector.debit]] + payment_method_type = "JCB" +[[dummy_connector.debit]] + payment_method_type = "DinersClub" +[[dummy_connector.debit]] + payment_method_type = "Discover" +[[dummy_connector.debit]] + payment_method_type = "CartesBancaires" +[[dummy_connector.debit]] + payment_method_type = "UnionPay" +[dummy_connector.connector_auth.HeaderKey] +api_key="Api Key" + +[paypal_test] +[[paypal_test.credit]] + payment_method_type = "Mastercard" +[[paypal_test.credit]] + payment_method_type = "Visa" +[[paypal_test.credit]] + payment_method_type = "Interac" +[[paypal_test.credit]] + payment_method_type = "AmericanExpress" +[[paypal_test.credit]] + payment_method_type = "JCB" +[[paypal_test.credit]] + payment_method_type = "DinersClub" +[[paypal_test.credit]] + payment_method_type = "Discover" +[[paypal_test.credit]] + payment_method_type = "CartesBancaires" +[[paypal_test.credit]] + payment_method_type = "UnionPay" +[[paypal_test.debit]] + payment_method_type = "Mastercard" +[[paypal_test.debit]] + payment_method_type = "Visa" +[[paypal_test.debit]] + payment_method_type = "Interac" +[[paypal_test.debit]] + payment_method_type = "AmericanExpress" +[[paypal_test.debit]] + payment_method_type = "JCB" +[[paypal_test.debit]] + payment_method_type = "DinersClub" +[[paypal_test.debit]] + payment_method_type = "Discover" +[[paypal_test.debit]] + payment_method_type = "CartesBancaires" +[[paypal_test.debit]] + payment_method_type = "UnionPay" +[[paypal_test.wallet]] + payment_method_type = "paypal" +[paypal_test.connector_auth.HeaderKey] +api_key="Api Key" + +[stripe_test] +[[stripe_test.credit]] + payment_method_type = "Mastercard" +[[stripe_test.credit]] + payment_method_type = "Visa" +[[stripe_test.credit]] + payment_method_type = "Interac" +[[stripe_test.credit]] + payment_method_type = "AmericanExpress" +[[stripe_test.credit]] + payment_method_type = "JCB" +[[stripe_test.credit]] + payment_method_type = "DinersClub" +[[stripe_test.credit]] + payment_method_type = "Discover" +[[stripe_test.credit]] + payment_method_type = "CartesBancaires" +[[stripe_test.credit]] + payment_method_type = "UnionPay" +[[stripe_test.debit]] + payment_method_type = "Mastercard" +[[stripe_test.debit]] + payment_method_type = "Visa" +[[stripe_test.debit]] + payment_method_type = "Interac" +[[stripe_test.debit]] + payment_method_type = "AmericanExpress" +[[stripe_test.debit]] + payment_method_type = "JCB" +[[stripe_test.debit]] + payment_method_type = "DinersClub" +[[stripe_test.debit]] + payment_method_type = "Discover" +[[stripe_test.debit]] + payment_method_type = "CartesBancaires" +[[stripe_test.debit]] + payment_method_type = "UnionPay" +[[stripe_test.wallet]] + payment_method_type = "google_pay" +[[stripe_test.wallet]] + payment_method_type = "ali_pay" +[[stripe_test.wallet]] + payment_method_type = "we_chat_pay" +[[stripe_test.pay_later]] + payment_method_type = "klarna" +[[stripe_test.pay_later]] + payment_method_type = "affirm" +[[stripe_test.pay_later]] + payment_method_type = "afterpay_clearpay" +[[paypal_test.wallet]] + payment_method_type = "paypal" +[stripe_test.connector_auth.HeaderKey] +api_key="Api Key" + +[helcim] +[[helcim.credit]] + payment_method_type = "Mastercard" +[[helcim.credit]] + payment_method_type = "Visa" +[[helcim.credit]] + payment_method_type = "Interac" +[[helcim.credit]] + payment_method_type = "AmericanExpress" +[[helcim.credit]] + payment_method_type = "JCB" +[[helcim.credit]] + payment_method_type = "DinersClub" +[[helcim.credit]] + payment_method_type = "Discover" +[[helcim.credit]] + payment_method_type = "CartesBancaires" +[[helcim.credit]] + payment_method_type = "UnionPay" +[[helcim.debit]] + payment_method_type = "Mastercard" +[[helcim.debit]] + payment_method_type = "Visa" +[[helcim.debit]] + payment_method_type = "Interac" +[[helcim.debit]] + payment_method_type = "AmericanExpress" +[[helcim.debit]] + payment_method_type = "JCB" +[[helcim.debit]] + payment_method_type = "DinersClub" +[[helcim.debit]] + payment_method_type = "Discover" +[[helcim.debit]] + payment_method_type = "CartesBancaires" +[[helcim.debit]] + payment_method_type = "UnionPay" +[helcim.connector_auth.HeaderKey] +api_key="Api Key" + + + + +[adyen_payout] +[[adyen_payout.credit]] + payment_method_type = "Mastercard" +[[adyen_payout.credit]] + payment_method_type = "Visa" +[[adyen_payout.credit]] + payment_method_type = "Interac" +[[adyen_payout.credit]] + payment_method_type = "AmericanExpress" +[[adyen_payout.credit]] + payment_method_type = "JCB" +[[adyen_payout.credit]] + payment_method_type = "DinersClub" +[[adyen_payout.credit]] + payment_method_type = "Discover" +[[adyen_payout.credit]] + payment_method_type = "CartesBancaires" +[[adyen_payout.credit]] + payment_method_type = "UnionPay" +[[adyen_payout.debit]] + payment_method_type = "Mastercard" +[[adyen_payout.debit]] + payment_method_type = "Visa" +[[adyen_payout.debit]] + payment_method_type = "Interac" +[[adyen_payout.debit]] + payment_method_type = "AmericanExpress" +[[adyen_payout.debit]] + payment_method_type = "JCB" +[[adyen_payout.debit]] + payment_method_type = "DinersClub" +[[adyen_payout.debit]] + payment_method_type = "Discover" +[[adyen_payout.debit]] + payment_method_type = "CartesBancaires" +[[adyen_payout.debit]] + payment_method_type = "UnionPay" +[[adyen_payout.bank_transfer]] + payment_method_type = "ach" +[[adyen_payout.bank_transfer]] + payment_method_type = "bacs" +[[adyen_payout.bank_transfer]] + payment_method_type = "sepa" +[adyen_payout.connector_auth.SignatureKey] +api_key = "Adyen API Key (Payout creation)" +api_secret = "Adyen Key (Payout submission)" +key1 = "Adyen Account Id" + +[wise_payout] +[[wise_payout.bank_transfer]] + payment_method_type = "ach" +[[wise_payout.bank_transfer]] + payment_method_type = "bacs" +[[wise_payout.bank_transfer]] + payment_method_type = "sepa" +[wise_payout.connector_auth.BodyKey] +api_key = "Wise API Key" +key1 = "Wise Account Id" \ No newline at end of file diff --git a/crates/currency_conversion/Cargo.toml b/crates/currency_conversion/Cargo.toml index 7eb3af7d526..d84956fe2f7 100644 --- a/crates/currency_conversion/Cargo.toml +++ b/crates/currency_conversion/Cargo.toml @@ -12,5 +12,5 @@ common_enums = { version = "0.1.0", path = "../common_enums", package = "common_ # Third party crates rust_decimal = "1.29" rusty-money = { version = "0.4.0", features = ["iso", "crypto"] } -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.43" diff --git a/crates/data_models/Cargo.toml b/crates/data_models/Cargo.toml index a86dc3070b4..39bcc71341b 100644 --- a/crates/data_models/Cargo.toml +++ b/crates/data_models/Cargo.toml @@ -22,7 +22,7 @@ diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_ # Third party deps async-trait = "0.1.68" error-stack = "0.3.1" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/data_models/src/errors.rs b/crates/data_models/src/errors.rs index 9616a3a944c..bed1ab9ccbf 100644 --- a/crates/data_models/src/errors.rs +++ b/crates/data_models/src/errors.rs @@ -24,6 +24,8 @@ pub enum StorageError { SerializationFailed, #[error("MockDb error")] MockDbError, + #[error("Kafka error")] + KafkaError, #[error("Customer with this id is Redacted")] CustomerRedacted, #[error("Deserialization failure")] diff --git a/crates/data_models/src/payments.rs b/crates/data_models/src/payments.rs index 7a4787fcf0a..713003d666b 100644 --- a/crates/data_models/src/payments.rs +++ b/crates/data_models/src/payments.rs @@ -50,7 +50,9 @@ pub struct PaymentIntent { pub updated_by: String, pub surcharge_applicable: Option, - pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, + pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub fingerprint_id: Option, + pub session_expiry: Option, } diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index f7b849f1d4e..3e6ba9e37f8 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -107,6 +107,7 @@ pub struct PaymentAttempt { pub attempt_id: String, pub status: storage_enums::AttemptStatus, pub amount: i64, + pub net_amount: i64, pub currency: Option, pub save_to_locker: Option, pub connector: Option, @@ -156,6 +157,16 @@ pub struct PaymentAttempt { pub unified_message: Option, } +impl PaymentAttempt { + pub fn get_total_amount(&self) -> i64 { + self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + } + pub fn get_total_surcharge_amount(&self) -> Option { + self.surcharge_amount + .map(|surcharge_amount| surcharge_amount + self.tax_amount.unwrap_or(0)) + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct PaymentListFilters { pub connector: Vec, @@ -173,6 +184,9 @@ pub struct PaymentAttemptNew { pub attempt_id: String, pub status: storage_enums::AttemptStatus, pub amount: i64, + /// amount + surcharge_amount + tax_amount + /// This field will always be derived before updating in the Database + pub net_amount: i64, pub currency: Option, // pub auto_capture: Option, pub save_to_locker: Option, @@ -220,6 +234,19 @@ pub struct PaymentAttemptNew { pub unified_message: Option, } +impl PaymentAttemptNew { + /// returns amount + surcharge_amount + tax_amount + pub fn calculate_net_amount(&self) -> i64 { + self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + } + + pub fn populate_derived_fields(self) -> Self { + let mut payment_attempt_new = self; + payment_attempt_new.net_amount = payment_attempt_new.calculate_net_amount(); + payment_attempt_new + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentAttemptUpdate { Update { diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index d7edcfdf179..7470b5f8502 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -107,9 +107,11 @@ pub struct PaymentIntentNew { pub updated_by: String, pub surcharge_applicable: Option, - pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, + pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub fingerprint_id: Option, + pub session_expiry: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -162,6 +164,8 @@ pub enum PaymentIntentUpdate { metadata: Option, payment_confirm_source: Option, updated_by: String, + fingerprint_id: Option, + session_expiry: Option, }, PaymentAttemptAndAttemptCountUpdate { active_attempt_id: String, @@ -226,6 +230,8 @@ pub struct PaymentIntentUpdateInternal { pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub fingerprint_id: Option, + pub session_expiry: Option, } impl From for PaymentIntentUpdateInternal { @@ -249,6 +255,8 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + fingerprint_id, + session_expiry, } => Self { amount: Some(amount), currency: Some(currency), @@ -268,6 +276,8 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + fingerprint_id, + session_expiry, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { diff --git a/crates/diesel_models/Cargo.toml b/crates/diesel_models/Cargo.toml index ccef0bf4e74..35a86f2e85c 100644 --- a/crates/diesel_models/Cargo.toml +++ b/crates/diesel_models/Cargo.toml @@ -17,8 +17,8 @@ diesel = { version = "2.1.0", features = ["postgres", "serde_json", "time", "64- error-stack = "0.3.1" frunk = "0.4.1" frunk_core = "0.4.1" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/diesel_models/src/authorization.rs b/crates/diesel_models/src/authorization.rs index 64fd1c65187..b6f75bbb9b7 100644 --- a/crates/diesel_models/src/authorization.rs +++ b/crates/diesel_models/src/authorization.rs @@ -57,6 +57,21 @@ pub struct AuthorizationUpdateInternal { pub connector_authorization_id: Option, } +impl AuthorizationUpdateInternal { + pub fn create_authorization(self, source: Authorization) -> Authorization { + Authorization { + status: self.status.unwrap_or(source.status), + error_code: self.error_code.or(source.error_code), + error_message: self.error_message.or(source.error_message), + modified_at: self.modified_at.unwrap_or(common_utils::date_time::now()), + connector_authorization_id: self + .connector_authorization_id + .or(source.connector_authorization_id), + ..source + } + } +} + impl From for AuthorizationUpdateInternal { fn from(authorization_child_update: AuthorizationUpdate) -> Self { let now = Some(common_utils::date_time::now()); diff --git a/crates/diesel_models/src/blocklist.rs b/crates/diesel_models/src/blocklist.rs new file mode 100644 index 00000000000..9e88802aa3b --- /dev/null +++ b/crates/diesel_models/src/blocklist.rs @@ -0,0 +1,26 @@ +use diesel::{Identifiable, Insertable, Queryable}; +use serde::{Deserialize, Serialize}; + +use crate::schema::blocklist; + +#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)] +#[diesel(table_name = blocklist)] +pub struct BlocklistNew { + pub merchant_id: String, + pub fingerprint_id: String, + pub data_kind: common_enums::BlocklistDataKind, + pub metadata: Option, + pub created_at: time::PrimitiveDateTime, +} + +#[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Deserialize, Serialize)] +#[diesel(table_name = blocklist)] +pub struct Blocklist { + #[serde(skip)] + pub id: i32, + pub merchant_id: String, + pub fingerprint_id: String, + pub data_kind: common_enums::BlocklistDataKind, + pub metadata: Option, + pub created_at: time::PrimitiveDateTime, +} diff --git a/crates/diesel_models/src/blocklist_fingerprint.rs b/crates/diesel_models/src/blocklist_fingerprint.rs new file mode 100644 index 00000000000..e75856622e2 --- /dev/null +++ b/crates/diesel_models/src/blocklist_fingerprint.rs @@ -0,0 +1,26 @@ +use diesel::{Identifiable, Insertable, Queryable}; +use serde::{Deserialize, Serialize}; + +use crate::schema::blocklist_fingerprint; + +#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)] +#[diesel(table_name = blocklist_fingerprint)] +pub struct BlocklistFingerprintNew { + pub merchant_id: String, + pub fingerprint_id: String, + pub data_kind: common_enums::BlocklistDataKind, + pub encrypted_fingerprint: String, + pub created_at: time::PrimitiveDateTime, +} + +#[derive(Clone, Debug, Eq, PartialEq, Queryable, Identifiable, Deserialize, Serialize)] +#[diesel(table_name = blocklist_fingerprint)] +pub struct BlocklistFingerprint { + #[serde(skip_serializing)] + pub id: i32, + pub merchant_id: String, + pub fingerprint_id: String, + pub data_kind: common_enums::BlocklistDataKind, + pub encrypted_fingerprint: String, + pub created_at: time::PrimitiveDateTime, +} diff --git a/crates/diesel_models/src/blocklist_lookup.rs b/crates/diesel_models/src/blocklist_lookup.rs new file mode 100644 index 00000000000..ad2a893e03d --- /dev/null +++ b/crates/diesel_models/src/blocklist_lookup.rs @@ -0,0 +1,20 @@ +use diesel::{Identifiable, Insertable, Queryable}; +use serde::{Deserialize, Serialize}; + +use crate::schema::blocklist_lookup; + +#[derive(Default, Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)] +#[diesel(table_name = blocklist_lookup)] +pub struct BlocklistLookupNew { + pub merchant_id: String, + pub fingerprint: String, +} + +#[derive(Default, Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Deserialize, Serialize)] +#[diesel(table_name = blocklist_lookup)] +pub struct BlocklistLookup { + #[serde(skip)] + pub id: i32, + pub merchant_id: String, + pub fingerprint: String, +} diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 700104aaaec..ad66eb7f6f1 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -32,6 +32,8 @@ pub struct BusinessProfile { pub is_recon_enabled: bool, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -55,6 +57,8 @@ pub struct BusinessProfileNew { pub is_recon_enabled: bool, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -75,6 +79,8 @@ pub struct BusinessProfileUpdateInternal { pub is_recon_enabled: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } impl From for BusinessProfile { @@ -97,6 +103,8 @@ impl From for BusinessProfile { payout_routing_algorithm: new.payout_routing_algorithm, is_recon_enabled: new.is_recon_enabled, applepay_verified_domains: new.applepay_verified_domains, + payment_link_config: new.payment_link_config, + session_expiry: new.session_expiry, } } } @@ -118,6 +126,8 @@ impl BusinessProfileUpdateInternal { payout_routing_algorithm, is_recon_enabled, applepay_verified_domains, + payment_link_config, + session_expiry, } = self; BusinessProfile { profile_name: profile_name.unwrap_or(source.profile_name), @@ -136,6 +146,8 @@ impl BusinessProfileUpdateInternal { payout_routing_algorithm, is_recon_enabled: is_recon_enabled.unwrap_or(source.is_recon_enabled), applepay_verified_domains, + payment_link_config, + session_expiry, ..source } } diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 17837d2ce5c..a06937c99a6 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -2,9 +2,10 @@ pub mod diesel_exports { pub use super::{ DbAttemptStatus as AttemptStatus, DbAuthenticationType as AuthenticationType, - DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus, - DbConnectorStatus as ConnectorStatus, DbConnectorType as ConnectorType, - DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, DbDisputeStage as DisputeStage, + DbBlocklistDataKind as BlocklistDataKind, DbCaptureMethod as CaptureMethod, + DbCaptureStatus as CaptureStatus, DbConnectorStatus as ConnectorStatus, + DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, + DbDashboardMetadata as DashboardMetadata, DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventClass as EventClass, DbEventObjectType as EventObjectType, DbEventType as EventType, DbFraudCheckStatus as FraudCheckStatus, DbFraudCheckType as FraudCheckType, @@ -16,7 +17,7 @@ pub mod diesel_exports { DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus, DbRefundStatus as RefundStatus, DbRefundType as RefundType, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, - DbRoutingAlgorithmKind as RoutingAlgorithmKind, + DbRoutingAlgorithmKind as RoutingAlgorithmKind, DbUserStatus as UserStatus, }; } pub use common_enums::*; @@ -418,7 +419,7 @@ pub enum FraudCheckLastStep { strum::EnumString, frunk::LabelledGeneric, )] -#[diesel_enum(storage_type = "text")] +#[diesel_enum(storage_type = "db_enum")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum UserStatus { @@ -439,7 +440,7 @@ pub enum UserStatus { strum::EnumString, frunk::LabelledGeneric, )] -#[router_derive::diesel_enum(storage_type = "text")] +#[router_derive::diesel_enum(storage_type = "db_enum")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum DashboardMetadata { diff --git a/crates/diesel_models/src/kv.rs b/crates/diesel_models/src/kv.rs index dd12a916c90..d067192e247 100644 --- a/crates/diesel_models/src/kv.rs +++ b/crates/diesel_models/src/kv.rs @@ -7,8 +7,8 @@ use crate::{ payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, payment_intent::{PaymentIntentNew, PaymentIntentUpdate}, refund::{Refund, RefundNew, RefundUpdate}, - reverse_lookup::ReverseLookupNew, - PaymentIntent, + reverse_lookup::{ReverseLookup, ReverseLookupNew}, + PaymentIntent, PgPooledConn, }; #[derive(Debug, Serialize, Deserialize)] @@ -16,7 +16,41 @@ use crate::{ pub enum DBOperation { Insert { insertable: Insertable }, Update { updatable: Updateable }, - Delete, +} + +impl DBOperation { + pub fn operation<'a>(&self) -> &'a str { + match self { + Self::Insert { .. } => "insert", + Self::Update { .. } => "update", + } + } + pub fn table<'a>(&self) -> &'a str { + match self { + Self::Insert { insertable } => match insertable { + Insertable::PaymentIntent(_) => "payment_intent", + Insertable::PaymentAttempt(_) => "payment_attempt", + Insertable::Refund(_) => "refund", + Insertable::Address(_) => "address", + Insertable::ReverseLookUp(_) => "reverse_lookup", + }, + Self::Update { updatable } => match updatable { + Updateable::PaymentIntentUpdate(_) => "payment_intent", + Updateable::PaymentAttemptUpdate(_) => "payment_attempt", + Updateable::RefundUpdate(_) => "refund", + Updateable::AddressUpdate(_) => "address", + }, + } + } +} + +#[derive(Debug)] +pub enum DBResult { + PaymentIntent(Box), + PaymentAttempt(Box), + Refund(Box), + Address(Box
), + ReverseLookUp(Box), } #[derive(Debug, Serialize, Deserialize)] @@ -25,6 +59,40 @@ pub struct TypedSql { pub op: DBOperation, } +impl DBOperation { + pub async fn execute(self, conn: &PgPooledConn) -> crate::StorageResult { + Ok(match self { + Self::Insert { insertable } => match insertable { + Insertable::PaymentIntent(a) => { + DBResult::PaymentIntent(Box::new(a.insert(conn).await?)) + } + Insertable::PaymentAttempt(a) => { + DBResult::PaymentAttempt(Box::new(a.insert(conn).await?)) + } + Insertable::Refund(a) => DBResult::Refund(Box::new(a.insert(conn).await?)), + Insertable::Address(addr) => DBResult::Address(Box::new(addr.insert(conn).await?)), + Insertable::ReverseLookUp(rev) => { + DBResult::ReverseLookUp(Box::new(rev.insert(conn).await?)) + } + }, + Self::Update { updatable } => match updatable { + Updateable::PaymentIntentUpdate(a) => { + DBResult::PaymentIntent(Box::new(a.orig.update(conn, a.update_data).await?)) + } + Updateable::PaymentAttemptUpdate(a) => DBResult::PaymentAttempt(Box::new( + a.orig.update_with_attempt_id(conn, a.update_data).await?, + )), + Updateable::RefundUpdate(a) => { + DBResult::Refund(Box::new(a.orig.update(conn, a.update_data).await?)) + } + Updateable::AddressUpdate(a) => { + DBResult::Address(Box::new(a.orig.update(conn, a.update_data).await?)) + } + }, + }) + } +} + impl TypedSql { pub fn to_field_value_pairs( &self, diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index fa32fb84a15..82b1e29ee83 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -1,11 +1,14 @@ pub mod address; pub mod api_keys; +pub mod blocklist_lookup; pub mod business_profile; pub mod capture; pub mod cards_info; pub mod configs; pub mod authorization; +pub mod blocklist; +pub mod blocklist_fingerprint; pub mod customers; pub mod dispute; pub mod encryption; diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index b1e8e144a9e..d08c146b0b8 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -63,6 +63,15 @@ pub struct PaymentAttempt { pub encoded_data: Option, pub unified_code: Option, pub unified_message: Option, + pub net_amount: Option, +} + +impl PaymentAttempt { + pub fn get_or_calculate_net_amount(&self) -> i64 { + self.net_amount.unwrap_or( + self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0), + ) + } } #[derive(Clone, Debug, Eq, PartialEq, Queryable, Serialize, Deserialize)] @@ -128,6 +137,25 @@ pub struct PaymentAttemptNew { pub encoded_data: Option, pub unified_code: Option, pub unified_message: Option, + pub net_amount: Option, +} + +impl PaymentAttemptNew { + /// returns amount + surcharge_amount + tax_amount + pub fn calculate_net_amount(&self) -> i64 { + self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + } + + pub fn get_or_calculate_net_amount(&self) -> i64 { + self.net_amount + .unwrap_or_else(|| self.calculate_net_amount()) + } + + pub fn populate_derived_fields(self) -> Self { + let mut payment_attempt_new = self; + payment_attempt_new.net_amount = Some(payment_attempt_new.calculate_net_amount()); + payment_attempt_new + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -279,6 +307,7 @@ pub enum PaymentAttemptUpdate { #[diesel(table_name = payment_attempt)] pub struct PaymentAttemptUpdateInternal { amount: Option, + net_amount: Option, currency: Option, status: Option, connector_transaction_id: Option, @@ -316,10 +345,29 @@ pub struct PaymentAttemptUpdateInternal { unified_message: Option>, } +impl PaymentAttemptUpdateInternal { + pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self { + let mut update_internal = self; + update_internal.net_amount = Some( + update_internal.amount.unwrap_or(source.amount) + + update_internal + .surcharge_amount + .or(source.surcharge_amount) + .unwrap_or(0) + + update_internal + .tax_amount + .or(source.tax_amount) + .unwrap_or(0), + ); + update_internal + } +} + impl PaymentAttemptUpdate { pub fn apply_changeset(self, source: PaymentAttempt) -> PaymentAttempt { let PaymentAttemptUpdateInternal { amount, + net_amount, currency, status, connector_transaction_id, @@ -355,9 +403,10 @@ impl PaymentAttemptUpdate { encoded_data, unified_code, unified_message, - } = self.into(); + } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), + net_amount: net_amount.or(source.net_amount), currency: currency.or(source.currency), status: status.unwrap_or(source.status), connector_transaction_id: connector_transaction_id.or(source.connector_transaction_id), diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 1bd5c73a96c..31bc0c06c51 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -52,21 +52,15 @@ pub struct PaymentIntent { pub updated_by: String, pub surcharge_applicable: Option, - pub request_incremental_authorization: RequestIncrementalAuthorization, + pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, + pub fingerprint_id: Option, } #[derive( - Clone, - Debug, - Default, - Eq, - PartialEq, - Insertable, - router_derive::DebugAsDisplay, - Serialize, - Deserialize, + Clone, Debug, Eq, PartialEq, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, )] #[diesel(table_name = payment_intent)] pub struct PaymentIntentNew { @@ -109,9 +103,12 @@ pub struct PaymentIntentNew { pub payment_confirm_source: Option, pub updated_by: String, pub surcharge_applicable: Option, - pub request_incremental_authorization: RequestIncrementalAuthorization, + pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub session_expiry: Option, + pub fingerprint_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -164,6 +161,8 @@ pub enum PaymentIntentUpdate { metadata: Option, payment_confirm_source: Option, updated_by: String, + session_expiry: Option, + fingerprint_id: Option, }, PaymentAttemptAndAttemptCountUpdate { active_attempt_id: String, @@ -229,6 +228,8 @@ pub struct PaymentIntentUpdateInternal { pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, + pub fingerprint_id: Option, } impl PaymentIntentUpdate { @@ -261,6 +262,8 @@ impl PaymentIntentUpdate { surcharge_applicable, incremental_authorization_allowed, authorization_count, + session_expiry, + fingerprint_id, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -291,8 +294,11 @@ impl PaymentIntentUpdate { updated_by, surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), - incremental_authorization_allowed, - authorization_count, + incremental_authorization_allowed: incremental_authorization_allowed + .or(source.incremental_authorization_allowed), + authorization_count: authorization_count.or(source.authorization_count), + fingerprint_id: fingerprint_id.or(source.fingerprint_id), + session_expiry: session_expiry.or(source.session_expiry), ..source } } @@ -319,6 +325,8 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, + fingerprint_id, } => Self { amount: Some(amount), currency: Some(currency), @@ -338,6 +346,8 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, + fingerprint_id, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { diff --git a/crates/diesel_models/src/payment_link.rs b/crates/diesel_models/src/payment_link.rs index 999a6767d8f..ed0e979d026 100644 --- a/crates/diesel_models/src/payment_link.rs +++ b/crates/diesel_models/src/payment_link.rs @@ -18,17 +18,17 @@ pub struct PaymentLink { pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] pub last_modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] pub fulfilment_time: Option, pub custom_merchant_name: Option, pub payment_link_config: Option, pub description: Option, + pub profile_id: Option, } #[derive( Clone, Debug, - Default, Eq, PartialEq, Insertable, @@ -48,9 +48,10 @@ pub struct PaymentLinkNew { pub created_at: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] pub last_modified_at: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] pub fulfilment_time: Option, pub custom_merchant_name: Option, pub payment_link_config: Option, pub description: Option, + pub profile_id: Option, } diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index 3a3dee47a85..3a0a008b76b 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -1,11 +1,14 @@ pub mod address; pub mod api_keys; +pub mod blocklist_lookup; pub mod business_profile; mod capture; pub mod cards_info; pub mod configs; pub mod authorization; +pub mod blocklist; +pub mod blocklist_fingerprint; pub mod customers; pub mod dashboard_metadata; pub mod dispute; diff --git a/crates/diesel_models/src/query/blocklist.rs b/crates/diesel_models/src/query/blocklist.rs new file mode 100644 index 00000000000..e1ba5fa923d --- /dev/null +++ b/crates/diesel_models/src/query/blocklist.rs @@ -0,0 +1,83 @@ +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use router_env::{instrument, tracing}; + +use super::generics; +use crate::{ + blocklist::{Blocklist, BlocklistNew}, + schema::blocklist::dsl, + PgPooledConn, StorageResult, +}; + +impl BlocklistNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Blocklist { + #[instrument(skip(conn))] + pub async fn find_by_merchant_id_fingerprint_id( + conn: &PgPooledConn, + merchant_id: &str, + fingerprint_id: &str, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())), + ) + .await + } + + #[instrument(skip(conn))] + pub async fn list_by_merchant_id_data_kind( + conn: &PgPooledConn, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, + limit: i64, + offset: i64, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::data_kind.eq(data_kind.to_owned())), + Some(limit), + Some(offset), + Some(dsl::created_at.desc()), + ) + .await + } + + #[instrument(skip(conn))] + pub async fn list_by_merchant_id( + conn: &PgPooledConn, + merchant_id: &str, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::merchant_id.eq(merchant_id.to_owned()), + None, + None, + Some(dsl::created_at.desc()), + ) + .await + } + + #[instrument(skip(conn))] + pub async fn delete_by_merchant_id_fingerprint_id( + conn: &PgPooledConn, + merchant_id: &str, + fingerprint_id: &str, + ) -> StorageResult { + generics::generic_delete_one_with_result::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/blocklist_fingerprint.rs b/crates/diesel_models/src/query/blocklist_fingerprint.rs new file mode 100644 index 00000000000..4f3d77e63a8 --- /dev/null +++ b/crates/diesel_models/src/query/blocklist_fingerprint.rs @@ -0,0 +1,33 @@ +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use router_env::{instrument, tracing}; + +use super::generics; +use crate::{ + blocklist_fingerprint::{BlocklistFingerprint, BlocklistFingerprintNew}, + schema::blocklist_fingerprint::dsl, + PgPooledConn, StorageResult, +}; + +impl BlocklistFingerprintNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl BlocklistFingerprint { + #[instrument(skip(conn))] + pub async fn find_by_merchant_id_fingerprint_id( + conn: &PgPooledConn, + merchant_id: &str, + fingerprint_id: &str, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/blocklist_lookup.rs b/crates/diesel_models/src/query/blocklist_lookup.rs new file mode 100644 index 00000000000..ea28c94e491 --- /dev/null +++ b/crates/diesel_models/src/query/blocklist_lookup.rs @@ -0,0 +1,48 @@ +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use router_env::{instrument, tracing}; + +use super::generics; +use crate::{ + blocklist_lookup::{BlocklistLookup, BlocklistLookupNew}, + schema::blocklist_lookup::dsl, + PgPooledConn, StorageResult, +}; + +impl BlocklistLookupNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl BlocklistLookup { + #[instrument(skip(conn))] + pub async fn find_by_merchant_id_fingerprint( + conn: &PgPooledConn, + merchant_id: &str, + fingerprint: &str, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::fingerprint.eq(fingerprint.to_owned())), + ) + .await + } + + #[instrument(skip(conn))] + pub async fn delete_by_merchant_id_fingerprint( + conn: &PgPooledConn, + merchant_id: &str, + fingerprint: &str, + ) -> StorageResult { + generics::generic_delete_one_with_result::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::fingerprint.eq(fingerprint.to_owned())), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index ef9a4165d6f..ba20f2e3660 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -110,4 +110,24 @@ impl MerchantAccount { ) .await } + + #[instrument(skip_all)] + pub async fn list_multiple_merchant_accounts( + conn: &PgPooledConn, + merchant_ids: Vec, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl::merchant_id.eq_any(merchant_ids), + None, + None, + None, + ) + .await + } } diff --git a/crates/diesel_models/src/query/merchant_key_store.rs b/crates/diesel_models/src/query/merchant_key_store.rs index 27ec3be9fcd..0e2ec1ddadb 100644 --- a/crates/diesel_models/src/query/merchant_key_store.rs +++ b/crates/diesel_models/src/query/merchant_key_store.rs @@ -39,4 +39,24 @@ impl MerchantKeyStore { ) .await } + + #[instrument(skip(conn))] + pub async fn list_multiple_key_stores( + conn: &PgPooledConn, + merchant_ids: Vec, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as diesel::Table>::PrimaryKey, + _, + >( + conn, + dsl::merchant_id.eq_any(merchant_ids), + None, + None, + None, + ) + .await + } } diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 9e9195f5e0b..ebd0dcd7351 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -23,7 +23,7 @@ use crate::{ impl PaymentAttemptNew { #[instrument(skip(conn))] pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { - generics::generic_insert(conn, self).await + generics::generic_insert(conn, self.populate_derived_fields()).await } } @@ -44,7 +44,7 @@ impl PaymentAttempt { dsl::attempt_id .eq(self.attempt_id.to_owned()) .and(dsl::merchant_id.eq(self.merchant_id.to_owned())), - PaymentAttemptUpdateInternal::from(payment_attempt), + PaymentAttemptUpdateInternal::from(payment_attempt).populate_derived_fields(&self), ) .await { diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 9baf613d923..131d2b18266 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -57,6 +57,50 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + blocklist (id) { + id -> Int4, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + fingerprint_id -> Varchar, + data_kind -> BlocklistDataKind, + metadata -> Nullable, + created_at -> Timestamp, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + blocklist_fingerprint (id) { + id -> Int4, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + fingerprint_id -> Varchar, + data_kind -> BlocklistDataKind, + encrypted_fingerprint -> Text, + created_at -> Timestamp, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + blocklist_lookup (id) { + id -> Int4, + #[max_length = 64] + merchant_id -> Varchar, + fingerprint -> Text, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -83,6 +127,8 @@ diesel::table! { payout_routing_algorithm -> Nullable, is_recon_enabled -> Bool, applepay_verified_domains -> Nullable>>, + payment_link_config -> Nullable, + session_expiry -> Nullable, } } @@ -195,8 +241,7 @@ diesel::table! { merchant_id -> Varchar, #[max_length = 64] org_id -> Varchar, - #[max_length = 64] - data_key -> Varchar, + data_key -> DashboardMetadata, data_value -> Json, #[max_length = 64] created_by -> Varchar, @@ -642,6 +687,7 @@ diesel::table! { unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, + net_amount -> Nullable, } } @@ -703,9 +749,12 @@ diesel::table! { #[max_length = 32] updated_by -> Varchar, surcharge_applicable -> Nullable, - request_incremental_authorization -> RequestIncrementalAuthorization, + request_incremental_authorization -> Nullable, incremental_authorization_allowed -> Nullable, authorization_count -> Nullable, + session_expiry -> Nullable, + #[max_length = 64] + fingerprint_id -> Nullable, } } @@ -732,6 +781,8 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 255] description -> Nullable, + #[max_length = 64] + profile_id -> Nullable, } } @@ -978,14 +1029,13 @@ diesel::table! { role_id -> Varchar, #[max_length = 64] org_id -> Varchar, - #[max_length = 64] - status -> Varchar, + status -> UserStatus, #[max_length = 64] created_by -> Varchar, #[max_length = 64] last_modified_by -> Varchar, created_at -> Timestamp, - last_modified_at -> Timestamp, + last_modified -> Timestamp, } } @@ -1012,6 +1062,9 @@ diesel::table! { diesel::allow_tables_to_appear_in_same_query!( address, api_keys, + blocklist, + blocklist_fingerprint, + blocklist_lookup, business_profile, captures, cards_info, diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 959d1ad9ee7..5a1ea696c56 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -62,6 +62,7 @@ pub struct PaymentAttemptBatchNew { pub encoded_data: Option, pub unified_code: Option, pub unified_message: Option, + pub net_amount: Option, } #[allow(dead_code)] @@ -114,6 +115,7 @@ impl PaymentAttemptBatchNew { encoded_data: self.encoded_data, unified_code: self.unified_code, unified_message: self.unified_message, + net_amount: self.net_amount, } } } diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs index 467584ac59d..3c32092fa91 100644 --- a/crates/diesel_models/src/user_role.rs +++ b/crates/diesel_models/src/user_role.rs @@ -15,7 +15,7 @@ pub struct UserRole { pub created_by: String, pub last_modified_by: String, pub created_at: PrimitiveDateTime, - pub last_modified_at: PrimitiveDateTime, + pub last_modified: PrimitiveDateTime, } #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -29,7 +29,7 @@ pub struct UserRoleNew { pub created_by: String, pub last_modified_by: String, pub created_at: PrimitiveDateTime, - pub last_modified_at: PrimitiveDateTime, + pub last_modified: PrimitiveDateTime, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] @@ -38,7 +38,7 @@ pub struct UserRoleUpdateInternal { role_id: Option, status: Option, last_modified_by: Option, - last_modified_at: PrimitiveDateTime, + last_modified: PrimitiveDateTime, } pub enum UserRoleUpdate { @@ -54,7 +54,7 @@ pub enum UserRoleUpdate { impl From for UserRoleUpdateInternal { fn from(value: UserRoleUpdate) -> Self { - let last_modified_at = common_utils::date_time::now(); + let last_modified = common_utils::date_time::now(); match value { UserRoleUpdate::UpdateRole { role_id, @@ -63,14 +63,14 @@ impl From for UserRoleUpdateInternal { role_id: Some(role_id), last_modified_by: Some(modified_by), status: None, - last_modified_at, + last_modified, }, UserRoleUpdate::UpdateStatus { status, modified_by, } => Self { status: Some(status), - last_modified_at, + last_modified, last_modified_by: Some(modified_by), role_id: None, }, diff --git a/crates/drainer/Cargo.toml b/crates/drainer/Cargo.toml index 668e8b0574f..50e0effd03e 100644 --- a/crates/drainer/Cargo.toml +++ b/crates/drainer/Cargo.toml @@ -20,11 +20,12 @@ config = { version = "0.13.3", features = ["toml"] } diesel = { version = "2.1.0", features = ["postgres"] } error-stack = "0.3.1" once_cell = "1.18.0" -serde = "1.0.163" -serde_json = "1.0.96" -serde_path_to_error = "0.1.11" +serde = "1.0.193" +serde_json = "1.0.108" +serde_path_to_error = "0.1.14" thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] } +async-trait = "0.1.74" # First Party Crates common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals"] } diff --git a/crates/drainer/src/errors.rs b/crates/drainer/src/errors.rs index 42ccfc7a0d8..3034e849f8a 100644 --- a/crates/drainer/src/errors.rs +++ b/crates/drainer/src/errors.rs @@ -11,6 +11,8 @@ pub enum DrainerError { ConfigurationError(config::ConfigError), #[error("Error while configuring signals: {0}")] SignalError(String), + #[error("Error while parsing data from the stream: {0:?}")] + ParsingError(error_stack::Report), #[error("Unexpected error occurred: {0}")] UnexpectedError(String), } diff --git a/crates/drainer/src/handler.rs b/crates/drainer/src/handler.rs new file mode 100644 index 00000000000..5aa902d84c5 --- /dev/null +++ b/crates/drainer/src/handler.rs @@ -0,0 +1,277 @@ +use std::sync::{atomic, Arc}; + +use tokio::{ + sync::{mpsc, oneshot}, + time::{self, Duration}, +}; + +use crate::{ + errors, instrument, logger, metrics, query::ExecuteQuery, tracing, utils, DrainerSettings, + Store, StreamData, +}; + +/// Handler handles the spawning and closing of drainer +/// Arc is used to enable creating a listener for graceful shutdown +#[derive(Clone)] +pub struct Handler { + inner: Arc, +} + +impl std::ops::Deref for Handler { + type Target = HandlerInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub struct HandlerInner { + shutdown_interval: Duration, + loop_interval: Duration, + active_tasks: Arc, + conf: DrainerSettings, + store: Arc, + running: Arc, +} + +impl Handler { + pub fn from_conf(conf: DrainerSettings, store: Arc) -> Self { + let shutdown_interval = Duration::from_millis(conf.shutdown_interval.into()); + let loop_interval = Duration::from_millis(conf.loop_interval.into()); + + let active_tasks = Arc::new(atomic::AtomicU64::new(0)); + + let running = Arc::new(atomic::AtomicBool::new(true)); + + let handler = HandlerInner { + shutdown_interval, + loop_interval, + active_tasks, + conf, + store, + running, + }; + + Self { + inner: Arc::new(handler), + } + } + + pub fn close(&self) { + self.running.store(false, atomic::Ordering::SeqCst); + } + + pub async fn spawn(&self) -> errors::DrainerResult<()> { + let mut stream_index: u8 = 0; + let jobs_picked = Arc::new(atomic::AtomicU8::new(0)); + + while self.running.load(atomic::Ordering::SeqCst) { + metrics::DRAINER_HEALTH.add(&metrics::CONTEXT, 1, &[]); + if self.store.is_stream_available(stream_index).await { + tokio::spawn(drainer_handler( + self.store.clone(), + stream_index, + self.conf.max_read_count, + self.active_tasks.clone(), + jobs_picked.clone(), + )); + } + stream_index = utils::increment_stream_index( + (stream_index, jobs_picked.clone()), + self.store.config.drainer_num_partitions, + ) + .await; + time::sleep(self.loop_interval).await; + } + + Ok(()) + } + + pub(crate) async fn shutdown_listener(&self, mut rx: mpsc::Receiver<()>) { + while let Some(_c) = rx.recv().await { + logger::info!("Awaiting shutdown!"); + metrics::SHUTDOWN_SIGNAL_RECEIVED.add(&metrics::CONTEXT, 1, &[]); + let shutdown_started = tokio::time::Instant::now(); + rx.close(); + + //Check until the active tasks are zero. This does not include the tasks in the stream. + while self.active_tasks.load(atomic::Ordering::SeqCst) != 0 { + time::sleep(self.shutdown_interval).await; + } + logger::info!("Terminating drainer"); + metrics::SUCCESSFUL_SHUTDOWN.add(&metrics::CONTEXT, 1, &[]); + let shutdown_ended = shutdown_started.elapsed().as_secs_f64() * 1000f64; + metrics::CLEANUP_TIME.record(&metrics::CONTEXT, shutdown_ended, &[]); + self.close(); + } + logger::info!( + tasks_remaining = self.active_tasks.load(atomic::Ordering::SeqCst), + "Drainer shutdown successfully" + ) + } + + pub fn spawn_error_handlers(&self, tx: mpsc::Sender<()>) -> errors::DrainerResult<()> { + let (redis_error_tx, redis_error_rx) = oneshot::channel(); + + let redis_conn_clone = self.store.redis_conn.clone(); + + // Spawn a task to monitor if redis is down or not + tokio::spawn(async move { redis_conn_clone.on_error(redis_error_tx).await }); + + //Spawns a task to send shutdown signal if redis goes down + tokio::spawn(redis_error_receiver(redis_error_rx, tx)); + + Ok(()) + } +} + +pub async fn redis_error_receiver(rx: oneshot::Receiver<()>, shutdown_channel: mpsc::Sender<()>) { + match rx.await { + Ok(_) => { + logger::error!("The redis server failed "); + let _ = shutdown_channel.send(()).await.map_err(|err| { + logger::error!("Failed to send signal to the shutdown channel {err}") + }); + } + Err(err) => { + logger::error!("Channel receiver error{err}"); + } + } +} + +#[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 = store.get_drainer_stream_name(stream_index); + + 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) + } + + let flag_stream_name = store.get_stream_key_flag(stream_index); + + let output = store.make_stream_available(flag_stream_name.as_str()).await; + active_tasks.fetch_sub(1, atomic::Ordering::Release); + output.map_err(|err| { + logger::error!(operation = "unlock_stream", err=?err); + err + }) +} + +#[instrument(skip_all, fields(global_id, request_id, session_id))] +async fn drainer( + store: Arc, + max_read_count: u64, + stream_name: &str, + jobs_picked: Arc, +) -> errors::DrainerResult<()> { + let stream_read = match store.read_from_stream(stream_name, max_read_count).await { + 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 = + redis_err.current_context() + { + metrics::STREAM_EMPTY.add(&metrics::CONTEXT, 1, &[]); + return Ok(()); + } else { + return Err(error); + } + } else { + return Err(error); + } + } + }; + + // parse_stream_entries returns error if no entries is found, handle it + let entries = utils::parse_stream_entries(&stream_read, stream_name)?; + let read_count = entries.len(); + + metrics::JOBS_PICKED_PER_STREAM.add( + &metrics::CONTEXT, + u64::try_from(read_count).unwrap_or(u64::MIN), + &[metrics::KeyValue { + key: "stream".into(), + value: stream_name.to_string().into(), + }], + ); + + let session_id = common_utils::generate_id_with_default_len("drainer_session"); + + let mut last_processed_id = String::new(); + + for (entry_id, entry) in entries.clone() { + let data = match StreamData::from_hashmap(entry) { + Ok(data) => data, + Err(err) => { + logger::error!(operation = "deserialization", err=?err); + metrics::STREAM_PARSE_FAIL.add( + &metrics::CONTEXT, + 1, + &[metrics::KeyValue { + key: "operation".into(), + value: "deserialization".into(), + }], + ); + + // break from the loop in case of a deser error + break; + } + }; + + tracing::Span::current().record("request_id", data.request_id); + tracing::Span::current().record("global_id", data.global_id); + tracing::Span::current().record("session_id", &session_id); + + match data.typed_sql.execute_query(&store, data.pushed_at).await { + Ok(_) => { + last_processed_id = entry_id; + } + Err(err) => match err.current_context() { + // In case of Uniqueviolation we can't really do anything to fix it so just clear + // it from the stream + diesel_models::errors::DatabaseError::UniqueViolation => { + last_processed_id = entry_id; + } + // break from the loop in case of an error in query + _ => break, + }, + } + } + + if !last_processed_id.is_empty() { + let entries_trimmed = store + .trim_from_stream(stream_name, &last_processed_id) + .await?; + if read_count != entries_trimmed { + logger::error!( + read_entries = %read_count, + trimmed_entries = %entries_trimmed, + ?entries, + "Assertion Failed no. of entries read from the stream doesn't match no. of entries trimmed" + ); + } + } else { + logger::error!(read_entries = %read_count,?entries,"No streams were processed in this session"); + } + + Ok(()) +} diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 796c9aa6955..abb32c87796 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -1,34 +1,30 @@ mod connection; pub mod errors; +mod handler; pub mod logger; pub(crate) mod metrics; +mod query; pub mod services; pub mod settings; +mod stream; +mod types; mod utils; -use std::sync::{atomic, Arc}; +use std::sync::Arc; -use common_utils::{ext_traits::StringExt, signals::get_allowed_signals}; +use common_utils::signals::get_allowed_signals; use diesel_models::kv; use error_stack::{IntoReport, ResultExt}; use router_env::{instrument, tracing}; -use tokio::sync::{mpsc, oneshot}; +use tokio::sync::mpsc; -use crate::{connection::pg_connection, services::Store}; +use crate::{ + connection::pg_connection, services::Store, settings::DrainerSettings, types::StreamData, +}; -pub async fn start_drainer( - store: Arc, - number_of_streams: u8, - max_read_count: u64, - shutdown_interval: u32, - loop_interval: u32, -) -> errors::DrainerResult<()> { - let mut stream_index: u8 = 0; - let jobs_picked = Arc::new(atomic::AtomicU8::new(0)); +pub async fn start_drainer(store: Arc, conf: DrainerSettings) -> errors::DrainerResult<()> { + let drainer_handler = handler::Handler::from_conf(conf, store); - let mut shutdown_interval = - tokio::time::interval(std::time::Duration::from_millis(shutdown_interval.into())); - let mut loop_interval = - tokio::time::interval(std::time::Duration::from_millis(loop_interval.into())); + let (tx, rx) = mpsc::channel::<()>(1); let signal = get_allowed_signals() @@ -36,328 +32,20 @@ pub async fn start_drainer( .change_context(errors::DrainerError::SignalError( "Failed while getting allowed signals".to_string(), ))?; - - let (redis_error_tx, redis_error_rx) = oneshot::channel(); - let (tx, mut rx) = mpsc::channel(1); let handle = signal.handle(); let task_handle = tokio::spawn(common_utils::signals::signal_handler(signal, tx.clone())); - let redis_conn_clone = store.redis_conn.clone(); + let handler_clone = drainer_handler.clone(); - // Spawn a task to monitor if redis is down or not - tokio::spawn(async move { redis_conn_clone.on_error(redis_error_tx).await }); + tokio::task::spawn(async move { handler_clone.shutdown_listener(rx).await }); - //Spawns a task to send shutdown signal if redis goes down - tokio::spawn(redis_error_receiver(redis_error_rx, tx)); + drainer_handler.spawn_error_handlers(tx)?; + drainer_handler.spawn().await?; - let active_tasks = Arc::new(atomic::AtomicU64::new(0)); - 'event: loop { - metrics::DRAINER_HEALTH.add(&metrics::CONTEXT, 1, &[]); - match rx.try_recv() { - Err(mpsc::error::TryRecvError::Empty) => { - if utils::is_stream_available(stream_index, store.clone()).await { - tokio::spawn(drainer_handler( - store.clone(), - stream_index, - max_read_count, - active_tasks.clone(), - jobs_picked.clone(), - )); - } - stream_index = utils::increment_stream_index( - (stream_index, jobs_picked.clone()), - number_of_streams, - ) - .await; - loop_interval.tick().await; - } - Ok(()) | Err(mpsc::error::TryRecvError::Disconnected) => { - logger::info!("Awaiting shutdown!"); - metrics::SHUTDOWN_SIGNAL_RECEIVED.add(&metrics::CONTEXT, 1, &[]); - let shutdown_started = tokio::time::Instant::now(); - rx.close(); - loop { - if active_tasks.load(atomic::Ordering::Acquire) == 0 { - logger::info!("Terminating drainer"); - metrics::SUCCESSFUL_SHUTDOWN.add(&metrics::CONTEXT, 1, &[]); - let shutdown_ended = shutdown_started.elapsed().as_secs_f64() * 1000f64; - metrics::CLEANUP_TIME.record(&metrics::CONTEXT, shutdown_ended, &[]); - break 'event; - } - shutdown_interval.tick().await; - } - } - } - } handle.close(); - task_handle + let _ = task_handle .await - .into_report() - .change_context(errors::DrainerError::UnexpectedError( - "Failed while joining signal handler".to_string(), - ))?; - - Ok(()) -} - -pub async fn redis_error_receiver(rx: oneshot::Receiver<()>, shutdown_channel: mpsc::Sender<()>) { - match rx.await { - Ok(_) => { - logger::error!("The redis server failed "); - let _ = shutdown_channel.send(()).await.map_err(|err| { - logger::error!("Failed to send signal to the shutdown channel {err}") - }); - } - Err(err) => { - logger::error!("Channel receiver error{err}"); - } - } -} - -#[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(), - jobs_picked, - )) - .await; - - if let Err(error) = drainer_result { - logger::error!(?error) - } - - let flag_stream_name = utils::get_stream_key_flag(store.clone(), stream_index); - - //TODO: USE THE RESULT FOR LOGGING - let output = - utils::make_stream_available(flag_stream_name.as_str(), store.redis_conn.as_ref()).await; - active_tasks.fetch_sub(1, atomic::Ordering::Release); - output -} - -#[instrument(skip_all, fields(global_id, request_id, session_id))] -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) => { - 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 = - redis_err.current_context() - { - metrics::STREAM_EMPTY.add(&metrics::CONTEXT, 1, &[]); - return Ok(()); - } else { - return Err(error); - } - } else { - return Err(error); - } - } - }; - // parse_stream_entries returns error if no entries is found, handle it - let (entries, last_entry_id) = utils::parse_stream_entries(&stream_read, stream_name)?; - let read_count = entries.len(); - - metrics::JOBS_PICKED_PER_STREAM.add( - &metrics::CONTEXT, - u64::try_from(read_count).unwrap_or(u64::MIN), - &[metrics::KeyValue { - key: "stream".into(), - value: stream_name.to_string().into(), - }], - ); - - let session_id = common_utils::generate_id_with_default_len("drainer_session"); - - // TODO: Handle errors when deserialization fails and when DB error occurs - for entry in entries { - let typed_sql = entry.1.get("typed_sql").map_or(String::new(), Clone::clone); - let request_id = entry - .1 - .get("request_id") - .map_or(String::new(), Clone::clone); - let global_id = entry.1.get("global_id").map_or(String::new(), Clone::clone); - let pushed_at = entry.1.get("pushed_at"); - - tracing::Span::current().record("request_id", request_id); - tracing::Span::current().record("global_id", global_id); - tracing::Span::current().record("session_id", &session_id); - - let result = typed_sql.parse_struct("DBOperation"); - - let db_op = match result { - Ok(f) => f, - Err(err) => { - logger::error!(operation= "deserialization",error = %err); - metrics::STREAM_PARSE_FAIL.add(&metrics::CONTEXT, 1, &[]); - continue; - } - }; - - let conn = pg_connection(&store.master_pool).await; - let insert_op = "insert"; - let update_op = "update"; - let payment_intent = "payment_intent"; - let payment_attempt = "payment_attempt"; - let refund = "refund"; - let reverse_lookup = "reverse_lookup"; - let address = "address"; - match db_op { - // TODO: Handle errors - kv::DBOperation::Insert { insertable } => { - let (_, execution_time) = common_utils::date_time::time_it(|| async { - match insertable { - kv::Insertable::PaymentIntent(a) => { - macro_util::handle_resp!( - a.insert(&conn).await, - insert_op, - payment_intent - ) - } - kv::Insertable::PaymentAttempt(a) => { - macro_util::handle_resp!( - a.insert(&conn).await, - insert_op, - payment_attempt - ) - } - kv::Insertable::Refund(a) => { - macro_util::handle_resp!(a.insert(&conn).await, insert_op, refund) - } - kv::Insertable::Address(addr) => { - macro_util::handle_resp!(addr.insert(&conn).await, insert_op, address) - } - kv::Insertable::ReverseLookUp(rev) => { - macro_util::handle_resp!( - rev.insert(&conn).await, - insert_op, - reverse_lookup - ) - } - } - }) - .await; - metrics::QUERY_EXECUTION_TIME.record( - &metrics::CONTEXT, - execution_time, - &[metrics::KeyValue { - key: "operation".into(), - value: insert_op.into(), - }], - ); - utils::push_drainer_delay(pushed_at, insert_op.to_string()); - } - kv::DBOperation::Update { updatable } => { - let (_, execution_time) = common_utils::date_time::time_it(|| async { - match updatable { - kv::Updateable::PaymentIntentUpdate(a) => { - macro_util::handle_resp!( - a.orig.update(&conn, a.update_data).await, - update_op, - payment_intent - ) - } - kv::Updateable::PaymentAttemptUpdate(a) => { - macro_util::handle_resp!( - a.orig.update_with_attempt_id(&conn, a.update_data).await, - update_op, - payment_attempt - ) - } - kv::Updateable::RefundUpdate(a) => { - macro_util::handle_resp!( - a.orig.update(&conn, a.update_data).await, - update_op, - refund - ) - } - kv::Updateable::AddressUpdate(a) => macro_util::handle_resp!( - a.orig.update(&conn, a.update_data).await, - update_op, - address - ), - } - }) - .await; - metrics::QUERY_EXECUTION_TIME.record( - &metrics::CONTEXT, - execution_time, - &[metrics::KeyValue { - key: "operation".into(), - value: update_op.into(), - }], - ); - utils::push_drainer_delay(pushed_at, update_op.to_string()); - } - kv::DBOperation::Delete => { - // [#224]: Implement this - logger::error!("Not implemented!"); - } - }; - } - - let entries_trimmed = - utils::trim_from_stream(stream_name, last_entry_id.as_str(), &store.redis_conn).await?; - - if read_count != entries_trimmed { - logger::error!( - read_entries = %read_count, - trimmed_entries = %entries_trimmed, - ?entries, - "Assertion Failed no. of entries read from the stream doesn't match no. of entries trimmed" - ); - } + .map_err(|err| logger::error!("Failed while joining signal handler: {:?}", err)); Ok(()) } - -mod macro_util { - - macro_rules! handle_resp { - ($result:expr,$op_type:expr, $table:expr) => { - match $result { - Ok(inner_result) => { - logger::info!(operation = %$op_type, table = %$table, ?inner_result); - metrics::SUCCESSFUL_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, &[ - metrics::KeyValue { - key: "operation".into(), - value: $table.into(), - } - ]); - } - Err(err) => { - logger::error!(operation = %$op_type, table = %$table, ?err); - metrics::ERRORS_WHILE_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, &[ - metrics::KeyValue { - key: "operation".into(), - value: $table.into(), - } - ]); - } - } - }; - } - pub(crate) use handle_resp; -} diff --git a/crates/drainer/src/main.rs b/crates/drainer/src/main.rs index 2b4142abc0c..34c1294d55f 100644 --- a/crates/drainer/src/main.rs +++ b/crates/drainer/src/main.rs @@ -15,10 +15,8 @@ async fn main() -> DrainerResult<()> { let store = services::Store::new(&conf, false).await; let store = std::sync::Arc::new(store); - let number_of_streams = store.config.drainer_num_partitions; - let max_read_count = conf.drainer.max_read_count; - let shutdown_intervals = conf.drainer.shutdown_interval; - let loop_interval = conf.drainer.loop_interval; + #[cfg(feature = "vergen")] + println!("Starting drainer (Version: {})", router_env::git_tag!()); let _guard = router_env::setup( &conf.log, @@ -29,14 +27,7 @@ async fn main() -> DrainerResult<()> { logger::debug!(startup_config=?conf); logger::info!("Drainer started [{:?}] [{:?}]", conf.drainer, conf.log); - start_drainer( - store.clone(), - number_of_streams, - max_read_count, - shutdown_intervals, - loop_interval, - ) - .await?; + start_drainer(store.clone(), conf.drainer).await?; Ok(()) } diff --git a/crates/drainer/src/query.rs b/crates/drainer/src/query.rs new file mode 100644 index 00000000000..f79291f3eae --- /dev/null +++ b/crates/drainer/src/query.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use common_utils::errors::CustomResult; +use diesel_models::errors::DatabaseError; + +use crate::{kv, logger, metrics, pg_connection, services::Store}; + +#[async_trait::async_trait] +pub trait ExecuteQuery { + async fn execute_query( + self, + store: &Arc, + pushed_at: i64, + ) -> CustomResult<(), DatabaseError>; +} + +#[async_trait::async_trait] +impl ExecuteQuery for kv::DBOperation { + async fn execute_query( + self, + store: &Arc, + pushed_at: i64, + ) -> CustomResult<(), DatabaseError> { + let conn = pg_connection(&store.master_pool).await; + let operation = self.operation(); + let table = self.table(); + + let tags: &[metrics::KeyValue] = &[ + metrics::KeyValue { + key: "operation".into(), + value: operation.into(), + }, + metrics::KeyValue { + key: "table".into(), + value: table.into(), + }, + ]; + + let (result, execution_time) = + common_utils::date_time::time_it(|| self.execute(&conn)).await; + + push_drainer_delay(pushed_at, operation, table, tags); + metrics::QUERY_EXECUTION_TIME.record(&metrics::CONTEXT, execution_time, tags); + + match result { + Ok(result) => { + logger::info!(operation = operation, table = table, ?result); + metrics::SUCCESSFUL_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, tags); + Ok(()) + } + Err(err) => { + logger::error!(operation = operation, table = table, ?err); + metrics::ERRORS_WHILE_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, tags); + Err(err) + } + } + } +} + +#[inline(always)] +fn push_drainer_delay(pushed_at: i64, operation: &str, table: &str, tags: &[metrics::KeyValue]) { + let drained_at = common_utils::date_time::now_unix_timestamp(); + let delay = drained_at - pushed_at; + + logger::debug!( + operation = operation, + table = table, + delay = format!("{delay} secs") + ); + + metrics::DRAINER_DELAY_SECONDS.record(&metrics::CONTEXT, delay, tags); +} diff --git a/crates/drainer/src/services.rs b/crates/drainer/src/services.rs index 73f66f27dbf..481fcc07221 100644 --- a/crates/drainer/src/services.rs +++ b/crates/drainer/src/services.rs @@ -34,9 +34,4 @@ impl Store { request_id: None, } } - - pub fn drainer_stream(&self, shard_key: &str) -> String { - // Example: {shard_5}_drainer_stream - format!("{{{}}}_{}", shard_key, self.config.drainer_stream_name,) - } } diff --git a/crates/drainer/src/stream.rs b/crates/drainer/src/stream.rs new file mode 100644 index 00000000000..b2775ac4ba1 --- /dev/null +++ b/crates/drainer/src/stream.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; + +use error_stack::IntoReport; +use redis_interface as redis; +use router_env::{logger, tracing}; + +use crate::{errors, metrics, Store}; + +pub type StreamEntries = Vec<(String, HashMap)>; +pub type StreamReadResult = HashMap; + +impl Store { + #[inline(always)] + pub fn drainer_stream(&self, shard_key: &str) -> String { + // Example: {shard_5}_drainer_stream + format!("{{{}}}_{}", shard_key, self.config.drainer_stream_name,) + } + + #[inline(always)] + pub(crate) fn get_stream_key_flag(&self, stream_index: u8) -> String { + format!("{}_in_use", self.get_drainer_stream_name(stream_index)) + } + + #[inline(always)] + pub(crate) fn get_drainer_stream_name(&self, stream_index: u8) -> String { + self.drainer_stream(format!("shard_{stream_index}").as_str()) + } + + #[router_env::instrument(skip_all)] + pub async fn is_stream_available(&self, stream_index: u8) -> bool { + let stream_key_flag = self.get_stream_key_flag(stream_index); + + match self + .redis_conn + .set_key_if_not_exists_with_expiry(stream_key_flag.as_str(), true, None) + .await + { + Ok(resp) => resp == redis::types::SetnxReply::KeySet, + Err(error) => { + logger::error!(operation="lock_stream",err=?error); + false + } + } + } + + pub async fn make_stream_available(&self, stream_name_flag: &str) -> errors::DrainerResult<()> { + match self.redis_conn.delete_key(stream_name_flag).await { + Ok(redis::DelReply::KeyDeleted) => Ok(()), + Ok(redis::DelReply::KeyNotDeleted) => { + logger::error!("Tried to unlock a stream which is already unlocked"); + Ok(()) + } + Err(error) => Err(errors::DrainerError::from(error).into()), + } + } + + pub async fn read_from_stream( + &self, + stream_name: &str, + max_read_count: u64, + ) -> errors::DrainerResult { + // "0-0" id gives first entry + let stream_id = "0-0"; + let (output, execution_time) = common_utils::date_time::time_it(|| async { + self.redis_conn + .stream_read_entries(stream_name, stream_id, Some(max_read_count)) + .await + .map_err(errors::DrainerError::from) + .into_report() + }) + .await; + + metrics::REDIS_STREAM_READ_TIME.record( + &metrics::CONTEXT, + execution_time, + &[metrics::KeyValue::new("stream", stream_name.to_owned())], + ); + + output + } + pub async fn trim_from_stream( + &self, + stream_name: &str, + minimum_entry_id: &str, + ) -> errors::DrainerResult { + let trim_kind = redis::StreamCapKind::MinID; + let trim_type = redis::StreamCapTrim::Exact; + let trim_id = minimum_entry_id; + let (trim_result, execution_time) = + common_utils::date_time::time_it::, _, _>(|| async { + let trim_result = self + .redis_conn + .stream_trim_entries(stream_name, (trim_kind, trim_type, trim_id)) + .await + .map_err(errors::DrainerError::from) + .into_report()?; + + // Since xtrim deletes entries below given id excluding the given id. + // Hence, deleting the minimum entry id + self.redis_conn + .stream_delete_entries(stream_name, minimum_entry_id) + .await + .map_err(errors::DrainerError::from) + .into_report()?; + + Ok(trim_result) + }) + .await; + + metrics::REDIS_STREAM_TRIM_TIME.record( + &metrics::CONTEXT, + execution_time, + &[metrics::KeyValue::new("stream", stream_name.to_owned())], + ); + + // adding 1 because we are deleting the given id too + Ok(trim_result? + 1) + } +} diff --git a/crates/drainer/src/types.rs b/crates/drainer/src/types.rs new file mode 100644 index 00000000000..f1ddf8ef27e --- /dev/null +++ b/crates/drainer/src/types.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use common_utils::errors; +use error_stack::{IntoReport, ResultExt}; +use serde::{de::value::MapDeserializer, Deserialize, Serialize}; + +use crate::{ + kv, + utils::{deserialize_db_op, deserialize_i64}, +}; + +#[derive(Deserialize, Serialize)] +pub struct StreamData { + pub request_id: String, + pub global_id: String, + #[serde(deserialize_with = "deserialize_db_op")] + pub typed_sql: kv::DBOperation, + #[serde(deserialize_with = "deserialize_i64")] + pub pushed_at: i64, +} + +impl StreamData { + pub fn from_hashmap( + hashmap: HashMap, + ) -> errors::CustomResult { + let iter = MapDeserializer::< + '_, + std::collections::hash_map::IntoIter, + serde_json::error::Error, + >::new(hashmap.into_iter()); + + Self::deserialize(iter) + .into_report() + .change_context(errors::ParsingError::StructParseFailure("StreamData")) + } +} diff --git a/crates/drainer/src/utils.rs b/crates/drainer/src/utils.rs index 5d3bd241d4d..e27e04c30e6 100644 --- a/crates/drainer/src/utils.rs +++ b/crates/drainer/src/utils.rs @@ -1,125 +1,20 @@ -use std::{ - collections::HashMap, - sync::{atomic, Arc}, -}; +use std::sync::{atomic, Arc}; use error_stack::IntoReport; use redis_interface as redis; +use serde::de::Deserialize; use crate::{ - errors::{self, DrainerError}, - logger, metrics, services, tracing, + errors, kv, metrics, + stream::{StreamEntries, StreamReadResult}, }; -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); - - match store - .redis_conn - .set_key_if_not_exists_with_expiry(stream_key_flag.as_str(), true, None) - .await - { - Ok(resp) => resp == redis::types::SetnxReply::KeySet, - Err(error) => { - logger::error!(?error); - // Add metrics or logs - false - } - } -} - -pub async fn read_from_stream( - stream_name: &str, - max_read_count: u64, - redis: &redis::RedisConnectionPool, -) -> errors::DrainerResult { - // "0-0" id gives first entry - let stream_id = "0-0"; - let (output, execution_time) = common_utils::date_time::time_it(|| async { - redis - .stream_read_entries(stream_name, stream_id, Some(max_read_count)) - .await - .map_err(DrainerError::from) - .into_report() - }) - .await; - - metrics::REDIS_STREAM_READ_TIME.record( - &metrics::CONTEXT, - execution_time, - &[metrics::KeyValue::new("stream", stream_name.to_owned())], - ); - - output -} - -pub async fn trim_from_stream( - stream_name: &str, - minimum_entry_id: &str, - redis: &redis::RedisConnectionPool, -) -> errors::DrainerResult { - let trim_kind = redis::StreamCapKind::MinID; - let trim_type = redis::StreamCapTrim::Exact; - let trim_id = minimum_entry_id; - let (trim_result, execution_time) = - common_utils::date_time::time_it::, _, _>(|| async { - let trim_result = redis - .stream_trim_entries(stream_name, (trim_kind, trim_type, trim_id)) - .await - .map_err(DrainerError::from) - .into_report()?; - - // Since xtrim deletes entries below given id excluding the given id. - // Hence, deleting the minimum entry id - redis - .stream_delete_entries(stream_name, minimum_entry_id) - .await - .map_err(DrainerError::from) - .into_report()?; - - Ok(trim_result) - }) - .await; - - metrics::REDIS_STREAM_TRIM_TIME.record( - &metrics::CONTEXT, - execution_time, - &[metrics::KeyValue::new("stream", stream_name.to_owned())], - ); - - // adding 1 because we are deleting the given id too - Ok(trim_result? + 1) -} - -pub async fn make_stream_available( - stream_name_flag: &str, - redis: &redis::RedisConnectionPool, -) -> errors::DrainerResult<()> { - match redis.delete_key(stream_name_flag).await { - Ok(redis::DelReply::KeyDeleted) => Ok(()), - Ok(redis::DelReply::KeyNotDeleted) => { - logger::error!("Tried to unlock a stream which is already unlocked"); - Ok(()) - } - Err(error) => Err(DrainerError::from(error).into()), - } -} - pub fn parse_stream_entries<'a>( read_result: &'a StreamReadResult, stream_name: &str, -) -> errors::DrainerResult<(&'a StreamEntries, String)> { +) -> errors::DrainerResult<&'a StreamEntries> { read_result .get(stream_name) - .and_then(|entries| { - entries - .last() - .map(|last_entry| (entries, last_entry.0.clone())) - }) .ok_or_else(|| { errors::DrainerError::RedisError(error_stack::report!( redis::errors::RedisError::NotFound @@ -128,27 +23,43 @@ pub fn parse_stream_entries<'a>( .into_report() } -pub fn push_drainer_delay(pushed_at: Option<&String>, operation: String) { - if let Some(pushed_at) = pushed_at { - if let Ok(time) = pushed_at.parse::() { - let drained_at = common_utils::date_time::now_unix_timestamp(); - let delay = drained_at - time; +pub(crate) fn deserialize_i64<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = serde_json::Value::deserialize(deserializer)?; + match s { + serde_json::Value::String(str_val) => str_val.parse().map_err(serde::de::Error::custom), + serde_json::Value::Number(num_val) => match num_val.as_i64() { + Some(val) => Ok(val), + None => Err(serde::de::Error::custom(format!( + "could not convert {num_val:?} to i64" + ))), + }, + other => Err(serde::de::Error::custom(format!( + "unexpected data format - expected string or number, got: {other:?}" + ))), + } +} - logger::debug!(operation = operation, delay = delay); - metrics::DRAINER_DELAY_SECONDS.record( - &metrics::CONTEXT, - delay, - &[metrics::KeyValue { - key: "operation".into(), - value: operation.into(), - }], - ); +pub(crate) fn deserialize_db_op<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = serde_json::Value::deserialize(deserializer)?; + match s { + serde_json::Value::String(str_val) => { + serde_json::from_str(&str_val).map_err(serde::de::Error::custom) } + other => Err(serde::de::Error::custom(format!( + "unexpected data format - expected string got: {other:?}" + ))), } } // Here the output is in the format (stream_index, jobs_picked), // similar to the first argument of the function +#[inline(always)] pub async fn increment_stream_index( (index, jobs_picked): (u8, Arc), total_streams: u8, @@ -164,11 +75,3 @@ pub async fn increment_stream_index( index + 1 } } - -pub(crate) fn get_stream_key_flag(store: Arc, stream_index: u8) -> String { - format!("{}_in_use", get_drainer_stream_name(store, stream_index)) -} - -pub(crate) fn get_drainer_stream_name(store: Arc, stream_index: u8) -> String { - store.drainer_stream(format!("shard_{stream_index}").as_str()) -} diff --git a/crates/euclid/Cargo.toml b/crates/euclid/Cargo.toml index 85979596414..08b9f0af28b 100644 --- a/crates/euclid/Cargo.toml +++ b/crates/euclid/Cargo.toml @@ -12,8 +12,8 @@ frunk_core = "0.4.1" nom = { version = "7.1.3", features = ["alloc"], optional = true } once_cell = "1.18.0" rustc-hash = "1.1.0" -serde = { version = "1.0.163", features = ["derive", "rc"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive", "rc"] } +serde_json = "1.0.108" strum = { version = "0.25", features = ["derive"] } thiserror = "1.0.43" diff --git a/crates/euclid/src/dssa/state_machine.rs b/crates/euclid/src/dssa/state_machine.rs index 4cd53911dfe..93d394eece9 100644 --- a/crates/euclid/src/dssa/state_machine.rs +++ b/crates/euclid/src/dssa/state_machine.rs @@ -678,7 +678,9 @@ mod tests { .collect::>(); assert_eq!( values, - expected_contexts[expected_idx] + expected_contexts + .get(expected_idx) + .expect("Error deriving contexts") .iter() .collect::>() ); @@ -702,7 +704,9 @@ mod tests { .collect::>(); assert_eq!( values, - expected_contexts[expected_idx] + expected_contexts + .get(expected_idx) + .expect("Error deriving contexts") .iter() .collect::>() ); diff --git a/crates/euclid/src/frontend/dir.rs b/crates/euclid/src/frontend/dir.rs index f8cef1f9295..dc81359c51d 100644 --- a/crates/euclid/src/frontend/dir.rs +++ b/crates/euclid/src/frontend/dir.rs @@ -722,7 +722,10 @@ mod test { }; let display_str = key.to_string(); - assert_eq!(&json_str[1..json_str.len() - 1], display_str); + assert_eq!( + json_str.get(1..json_str.len() - 1).expect("Value metadata"), + display_str + ); key_names.insert(key, display_str); } diff --git a/crates/euclid_macros/src/inner/knowledge.rs b/crates/euclid_macros/src/inner/knowledge.rs index 73b94919c90..9f33a6871c5 100644 --- a/crates/euclid_macros/src/inner/knowledge.rs +++ b/crates/euclid_macros/src/inner/knowledge.rs @@ -417,7 +417,10 @@ impl GenContext { .position(|v| *v == node_id) .ok_or_else(|| "Error deciding cycle order".to_string())?; - let cycle_order = order[position..].to_vec(); + let cycle_order = order + .get(position..) + .ok_or_else(|| "Error getting cycle order".to_string())? + .to_vec(); Ok(Some(cycle_order)) } else if visited.contains(&node_id) { Ok(None) diff --git a/crates/euclid_wasm/Cargo.toml b/crates/euclid_wasm/Cargo.toml index 8c96a7f67da..d9f5330a1b8 100644 --- a/crates/euclid_wasm/Cargo.toml +++ b/crates/euclid_wasm/Cargo.toml @@ -10,14 +10,19 @@ rust-version.workspace = true crate-type = ["cdylib"] [features] -default = ["connector_choice_bcompat"] +default = ["connector_choice_bcompat", "connector_choice_mca_id"] +release = ["connector_choice_bcompat", "connector_choice_mca_id"] connector_choice_bcompat = ["api_models/connector_choice_bcompat"] connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connector_choice_mca_id", "kgraph_utils/connector_choice_mca_id"] -dummy_connector = ["kgraph_utils/dummy_connector"] +dummy_connector = ["kgraph_utils/dummy_connector", "connector_configs/dummy_connector"] +production = ["connector_configs/production"] +development = ["connector_configs/development"] +sandbox = ["connector_configs/sandbox"] [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } currency_conversion = { version = "0.1.0", path = "../currency_conversion" } +connector_configs = { version = "0.1.0", path = "../connector_configs" } euclid = { path = "../euclid", features = [] } kgraph_utils = { version = "0.1.0", path = "../kgraph_utils" } common_enums = { version = "0.1.0", path = "../common_enums" } diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index 78c7677fe75..1143ea8a281 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -6,8 +6,15 @@ use std::{ str::FromStr, }; -use api_models::{admin as admin_api, routing::ConnectorSelection}; +use api_models::{ + admin as admin_api, conditional_configs::ConditionalConfigs, enums as api_model_enums, + routing::ConnectorSelection, surcharge_decision_configs::SurchargeDecisionConfigs, +}; use common_enums::RoutableConnectors; +use connector_configs::{ + common_config::{ConnectorApiIntegrationPayload, DashboardRequestPayload}, + connector, +}; use currency_conversion::{ conversion::convert as convert_currency, types as currency_conversion_types, }; @@ -20,7 +27,7 @@ use euclid::{ }, frontend::{ ast, - dir::{self, enums as dir_enums}, + dir::{self, enums as dir_enums, EuclidDirFilter}, }, }; use once_cell::sync::OnceCell; @@ -205,6 +212,18 @@ pub fn get_key_type(key: &str) -> Result { Ok(key_str) } +#[wasm_bindgen(js_name = getThreeDsKeys)] +pub fn get_three_ds_keys() -> JsResult { + let keys = ::ALLOWED; + Ok(serde_wasm_bindgen::to_value(keys)?) +} + +#[wasm_bindgen(js_name= getSurchargeKeys)] +pub fn get_surcharge_keys() -> JsResult { + let keys = ::ALLOWED; + Ok(serde_wasm_bindgen::to_value(keys)?) +} + #[wasm_bindgen(js_name=parseToString)] pub fn parser(val: String) -> String { ron_parser::my_parse(val) @@ -276,3 +295,35 @@ pub fn get_description_category() -> JsResult { Ok(serde_wasm_bindgen::to_value(&category)?) } + +#[wasm_bindgen(js_name = getConnectorConfig)] +pub fn get_connector_config(key: &str) -> JsResult { + let key = api_model_enums::Connector::from_str(key) + .map_err(|_| "Invalid key received".to_string())?; + let res = connector::ConnectorConfig::get_connector_config(key)?; + Ok(serde_wasm_bindgen::to_value(&res)?) +} + +#[cfg(feature = "payouts")] +#[wasm_bindgen(js_name = getPayoutConnectorConfig)] +pub fn get_payout_connector_config(key: &str) -> JsResult { + let key = api_model_enums::PayoutConnectors::from_str(key) + .map_err(|_| "Invalid key received".to_string())?; + let res = connector::ConnectorConfig::get_payout_connector_config(key)?; + Ok(serde_wasm_bindgen::to_value(&res)?) +} + +#[wasm_bindgen(js_name = getRequestPayload)] +pub fn get_request_payload(input: JsValue, response: JsValue) -> JsResult { + let input: DashboardRequestPayload = serde_wasm_bindgen::from_value(input)?; + let api_response: ConnectorApiIntegrationPayload = serde_wasm_bindgen::from_value(response)?; + let result = DashboardRequestPayload::create_connector_request(input, api_response); + Ok(serde_wasm_bindgen::to_value(&result)?) +} + +#[wasm_bindgen(js_name = getResponsePayload)] +pub fn get_response_payload(input: JsValue) -> JsResult { + let input: ConnectorApiIntegrationPayload = serde_wasm_bindgen::from_value(input)?; + let result = ConnectorApiIntegrationPayload::get_transformed_response_payload(input); + Ok(serde_wasm_bindgen::to_value(&result)?) +} diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index 54a636a382b..4767e4f8d25 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -22,7 +22,7 @@ base64 = "0.21.2" dyn-clone = "1.0.11" error-stack = "0.3.1" once_cell = "1.18.0" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.40" tokio = "1.28.2" hyper-proxy = "0.9.1" diff --git a/crates/external_services/src/email/ses.rs b/crates/external_services/src/email/ses.rs index 7e521a5bc1c..de4d7794918 100644 --- a/crates/external_services/src/email/ses.rs +++ b/crates/external_services/src/email/ses.rs @@ -12,14 +12,12 @@ use error_stack::{report, IntoReport, ResultExt}; use hyper::Uri; use masking::PeekInterface; use router_env::logger; -use tokio::sync::OnceCell; use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, IntermediateString}; /// Client for AWS SES operation #[derive(Debug, Clone)] pub struct AwsSes { - ses_client: OnceCell, sender: String, settings: EmailSettings, } @@ -70,13 +68,13 @@ pub enum AwsSesError { impl AwsSes { /// Constructs a new AwsSes client pub async fn create(conf: &EmailSettings, proxy_url: Option>) -> Self { + // Build the client initially which will help us know if the email configuration is correct + Self::create_client(conf, proxy_url) + .await + .map_err(|error| logger::error!(?error, "Failed to initialize SES Client")) + .ok(); + Self { - ses_client: OnceCell::new_with( - Self::create_client(conf, proxy_url) - .await - .map_err(|error| logger::error!(?error, "Failed to initialize SES Client")) - .ok(), - ), sender: conf.sender_email.clone(), settings: conf.clone(), } @@ -222,13 +220,13 @@ impl EmailClient for AwsSes { body: Self::RichText, proxy_url: Option<&String>, ) -> EmailResult<()> { - self.ses_client - .get_or_try_init(|| async { - Self::create_client(&self.settings, proxy_url) - .await - .change_context(EmailError::ClientBuildingFailure) - }) - .await? + // Not using the same email client which was created at startup as the role session would expire + // Create a client every time when the email is being sent + let email_client = Self::create_client(&self.settings, proxy_url) + .await + .change_context(EmailError::ClientBuildingFailure)?; + + email_client .send_email() .from_email_address(self.sender.to_owned()) .destination( diff --git a/crates/external_services/src/kms.rs b/crates/external_services/src/kms.rs index 31c82253fe8..04a58e4b23f 100644 --- a/crates/external_services/src/kms.rs +++ b/crates/external_services/src/kms.rs @@ -75,7 +75,7 @@ impl KmsClient { // Logging using `Debug` representation of the error as the `Display` // representation does not hold sufficient information. logger::error!(kms_sdk_error=?error, "Failed to KMS decrypt data"); - metrics::AWS_KMS_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::AWS_KMS_DECRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]); error }) .into_report() @@ -96,11 +96,51 @@ impl KmsClient { Ok(output) } + + /// Encrypts the provided String data using the AWS KMS SDK. We assume that + /// the SDK has the values required to interact with the AWS KMS APIs (`AWS_ACCESS_KEY_ID` and + /// `AWS_SECRET_ACCESS_KEY`) either set in environment variables, or that the SDK is running in + /// a machine that is able to assume an IAM role. + pub async fn encrypt(&self, data: impl AsRef<[u8]>) -> CustomResult { + let start = Instant::now(); + let plaintext_blob = Blob::new(data.as_ref()); + + let encrypted_output = self + .inner_client + .encrypt() + .key_id(&self.key_id) + .plaintext(plaintext_blob) + .send() + .await + .map_err(|error| { + // Logging using `Debug` representation of the error as the `Display` + // representation does not hold sufficient information. + logger::error!(kms_sdk_error=?error, "Failed to KMS encrypt data"); + metrics::AWS_KMS_ENCRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]); + error + }) + .into_report() + .change_context(KmsError::EncryptionFailed)?; + + let output = encrypted_output + .ciphertext_blob + .ok_or(KmsError::MissingCiphertextEncryptionOutput) + .into_report() + .map(|blob| consts::BASE64_ENGINE.encode(blob.into_inner()))?; + let time_taken = start.elapsed(); + metrics::AWS_KMS_ENCRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]); + + Ok(output) + } } /// Errors that could occur during KMS operations. #[derive(Debug, thiserror::Error)] pub enum KmsError { + /// An error occurred when base64 encoding input data. + #[error("Failed to base64 encode input data")] + Base64EncodingFailed, + /// An error occurred when base64 decoding input data. #[error("Failed to base64 decode input data")] Base64DecodingFailed, @@ -109,10 +149,18 @@ pub enum KmsError { #[error("Failed to KMS decrypt input data")] DecryptionFailed, + /// An error occurred when KMS encrypting input data. + #[error("Failed to KMS encrypt input data")] + EncryptionFailed, + /// The KMS decrypted output does not include a plaintext output. #[error("Missing plaintext KMS decryption output")] MissingPlaintextDecryptionOutput, + /// The KMS encrypted output does not include a ciphertext output. + #[error("Missing ciphertext KMS encryption output")] + MissingCiphertextEncryptionOutput, + /// An error occurred UTF-8 decoding KMS decrypted output. #[error("Failed to UTF-8 decode decryption output")] Utf8DecodingFailed, @@ -147,3 +195,50 @@ impl common_utils::ext_traits::ConfigExt for KmsValue { self.0.peek().is_empty_after_trim() } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + #[tokio::test] + async fn check_kms_encryption() { + std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY"); + std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID"); + use super::*; + let config = KmsConfig { + key_id: "YOUR KMS KEY ID".to_string(), + region: "AWS REGION".to_string(), + }; + + let data = "hello".to_string(); + let binding = data.as_bytes(); + let kms_encrypted_fingerprint = KmsClient::new(&config) + .await + .encrypt(binding) + .await + .expect("kms encryption failed"); + + println!("{}", kms_encrypted_fingerprint); + } + + #[tokio::test] + async fn check_kms_decrypt() { + std::env::set_var("AWS_SECRET_ACCESS_KEY", "YOUR SECRET ACCESS KEY"); + std::env::set_var("AWS_ACCESS_KEY_ID", "YOUR AWS ACCESS KEY ID"); + use super::*; + let config = KmsConfig { + key_id: "YOUR KMS KEY ID".to_string(), + region: "AWS REGION".to_string(), + }; + + // Should decrypt to hello + let data = "KMS ENCRYPTED CIPHER".to_string(); + let binding = data.as_bytes(); + let kms_encrypted_fingerprint = KmsClient::new(&config) + .await + .decrypt(binding) + .await + .expect("kms decryption failed"); + + println!("{}", kms_encrypted_fingerprint); + } +} diff --git a/crates/external_services/src/lib.rs b/crates/external_services/src/lib.rs index fa57a0bac9c..ccf1db47a3a 100644 --- a/crates/external_services/src/lib.rs +++ b/crates/external_services/src/lib.rs @@ -26,8 +26,12 @@ pub mod metrics { global_meter!(GLOBAL_METER, "EXTERNAL_SERVICES"); #[cfg(feature = "kms")] - counter_metric!(AWS_KMS_FAILURES, GLOBAL_METER); // No. of AWS KMS API failures + counter_metric!(AWS_KMS_DECRYPTION_FAILURES, GLOBAL_METER); // No. of AWS KMS Decryption failures + #[cfg(feature = "kms")] + counter_metric!(AWS_KMS_ENCRYPTION_FAILURES, GLOBAL_METER); // No. of AWS KMS Encryption failures #[cfg(feature = "kms")] histogram_metric!(AWS_KMS_DECRYPT_TIME, GLOBAL_METER); // Histogram for KMS decryption time (in sec) + #[cfg(feature = "kms")] + histogram_metric!(AWS_KMS_ENCRYPT_TIME, GLOBAL_METER); // Histogram for KMS encryption time (in sec) } diff --git a/crates/kgraph_utils/Cargo.toml b/crates/kgraph_utils/Cargo.toml index 44a73dae4d7..a07285167e4 100644 --- a/crates/kgraph_utils/Cargo.toml +++ b/crates/kgraph_utils/Cargo.toml @@ -16,8 +16,8 @@ euclid = { version = "0.1.0", path = "../euclid" } masking = { version = "0.1.0", path = "../masking/" } # Third party crates -serde = "1.0.163" -serde_json = "1.0.96" +serde = "1.0.193" +serde_json = "1.0.108" thiserror = "1.0.43" [dev-dependencies] diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 0e224a8f3d9..a04e052514d 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -156,7 +156,7 @@ fn compile_request_pm_types( let or_node_neighbor_id = if amount_nodes.len() == 1 { amount_nodes - .get(0) + .first() .copied() .ok_or(KgraphError::IndexingError)? } else { diff --git a/crates/masking/Cargo.toml b/crates/masking/Cargo.toml index bf92e867dc6..23f207d63d5 100644 --- a/crates/masking/Cargo.toml +++ b/crates/masking/Cargo.toml @@ -19,10 +19,11 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bytes = { version = "1", optional = true } diesel = { version = "2.1.0", features = ["postgres", "serde_json", "time"], optional = true } +erased-serde = "0.3.31" serde = { version = "1", features = ["derive"], optional = true } -serde_json = { version = "1.0.96", optional = true } +serde_json = { version = "1.0.108", optional = true } subtle = "=2.4.1" zeroize = { version = "1.6", default-features = false } [dev-dependencies] -serde_json = "1.0.96" +serde_json = "1.0.108" diff --git a/crates/masking/src/diesel.rs b/crates/masking/src/diesel.rs index 307f083d27f..f3576298bdb 100644 --- a/crates/masking/src/diesel.rs +++ b/crates/masking/src/diesel.rs @@ -2,7 +2,6 @@ //! Diesel-related. //! -pub use diesel::Expression; use diesel::{ backend::Backend, deserialize::{self, FromSql, Queryable}, diff --git a/crates/masking/src/serde.rs b/crates/masking/src/serde.rs index d1845ee2903..bb81717fd67 100644 --- a/crates/masking/src/serde.rs +++ b/crates/masking/src/serde.rs @@ -2,7 +2,8 @@ //! Serde-related. //! -pub use serde::{de, ser, Deserialize, Serialize, Serializer}; +pub use erased_serde::Serialize as ErasedSerialize; +pub use serde::{de, Deserialize, Serialize, Serializer}; use serde_json::{value::Serializer as JsonValueSerializer, Value}; use crate::{Secret, Strategy, StrongSecret, ZeroizableSecret}; @@ -99,20 +100,32 @@ pub fn masked_serialize(value: &T) -> Result because of Rust's "object safety" rules. /// In particular, the trait contains generic methods which cannot be made into a trait object. /// In this case we remove the generic for assuming the serialization to be of 2 types only raw json or masked json -pub trait ErasedMaskSerialize { +pub trait ErasedMaskSerialize: ErasedSerialize { /// Masked serialization. fn masked_serialize(&self) -> Result; - /// Normal serialization. - fn raw_serialize(&self) -> Result; } -impl ErasedMaskSerialize for T { +impl ErasedMaskSerialize for T { fn masked_serialize(&self) -> Result { masked_serialize(self) } +} + +impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + erased_serde::serialize(self, serializer) + } +} - fn raw_serialize(&self) -> Result { - serde_json::to_value(self) +impl<'a> Serialize for dyn ErasedMaskSerialize + 'a + Send { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + erased_serde::serialize(self, serializer) } } diff --git a/crates/masking/tests/basic.rs b/crates/masking/tests/basic.rs index 7857783f830..29ba90cbeae 100644 --- a/crates/masking/tests/basic.rs +++ b/crates/masking/tests/basic.rs @@ -117,7 +117,7 @@ fn without_serialize() -> Result<(), Box> { #[test] fn for_string() -> Result<(), Box> { - #[cfg_attr(feature = "serde", derive(Serialize))] + #[cfg_attr(all(feature = "alloc", feature = "serde"), derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Composite { secret_number: Secret, @@ -147,7 +147,7 @@ fn for_string() -> Result<(), Box> { // serialize - #[cfg(feature = "serde")] + #[cfg(all(feature = "alloc", feature = "serde"))] { let got = serde_json::to_string(&composite).unwrap(); let exp = r#"{"secret_number":"abc","not_secret":"not secret"}"#; diff --git a/crates/pm_auth/Cargo.toml b/crates/pm_auth/Cargo.toml index a9aebc5b540..9654932d5ef 100644 --- a/crates/pm_auth/Cargo.toml +++ b/crates/pm_auth/Cargo.toml @@ -21,7 +21,7 @@ bytes = "1.4.0" error-stack = "0.3.1" http = "0.2.9" mime = "0.3.17" -serde = "1.0.159" -serde_json = "1.0.91" +serde = "1.0.193" +serde_json = "1.0.108" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.43" diff --git a/crates/pm_auth/src/connector/plaid.rs b/crates/pm_auth/src/connector/plaid.rs index d25aba881d2..dfcfbb7eddc 100644 --- a/crates/pm_auth/src/connector/plaid.rs +++ b/crates/pm_auth/src/connector/plaid.rs @@ -3,8 +3,8 @@ pub mod transformers; use std::fmt::Debug; use common_utils::{ - ext_traits::{BytesExt, Encode}, - request::{Method, Request, RequestBody, RequestBuilder}, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, }; use error_stack::ResultExt; use masking::{Mask, Maskable}; @@ -121,14 +121,9 @@ impl ConnectorIntegration errors::CustomResult, errors::ConnectorError> { + ) -> errors::CustomResult { let req_obj = plaid::PlaidLinkTokenRequest::try_from(req)?; - let plaid_req = RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(plaid_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -146,7 +141,7 @@ impl ConnectorIntegration errors::CustomResult, errors::ConnectorError> { + ) -> errors::CustomResult { let req_obj = plaid::PlaidExchangeTokenRequest::try_from(req)?; - let plaid_req = RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(plaid_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -237,7 +227,7 @@ impl .headers(auth_types::PaymentAuthExchangeTokenType::get_headers( self, req, connectors, )?) - .body(auth_types::PaymentAuthExchangeTokenType::get_request_body( + .set_body(auth_types::PaymentAuthExchangeTokenType::get_request_body( self, req, )?) .build(), @@ -299,14 +289,9 @@ impl fn get_request_body( &self, req: &auth_types::BankDetailsRouterData, - ) -> errors::CustomResult, errors::ConnectorError> { + ) -> errors::CustomResult { let req_obj = plaid::PlaidBankAccountCredentialsRequest::try_from(req)?; - let plaid_req = RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(plaid_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -324,7 +309,9 @@ impl .headers(auth_types::PaymentAuthBankAccountDetailsType::get_headers( self, req, connectors, )?) - .body(auth_types::PaymentAuthBankAccountDetailsType::get_request_body(self, req)?) + .set_body( + auth_types::PaymentAuthBankAccountDetailsType::get_request_body(self, req)?, + ) .build(), )) } diff --git a/crates/pm_auth/src/types/api.rs b/crates/pm_auth/src/types/api.rs index 2416d0fee1d..3684e34ec05 100644 --- a/crates/pm_auth/src/types/api.rs +++ b/crates/pm_auth/src/types/api.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use common_utils::{ errors::CustomResult, - request::{Request, RequestBody}, + request::{Request, RequestContent}, }; use masking::Maskable; @@ -38,8 +38,8 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny, - ) -> CustomResult, ConnectorError> { - Ok(None) + ) -> CustomResult { + Ok(RequestContent::Json(Box::new(serde_json::json!(r#"{}"#)))) } fn build_request( diff --git a/crates/redis_interface/Cargo.toml b/crates/redis_interface/Cargo.toml index 9d3ae724d43..1a6bc96a7fc 100644 --- a/crates/redis_interface/Cargo.toml +++ b/crates/redis_interface/Cargo.toml @@ -9,11 +9,12 @@ license.workspace = true [dependencies] error-stack = "0.3.1" -fred = { version = "6.3.0", features = ["metrics", "partial-tracing", "subscriber-client"] } +fred = { version = "7.0.0", features = ["metrics", "partial-tracing", "subscriber-client"] } futures = "0.3" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.193", features = ["derive"] } thiserror = "1.0.40" tokio = "1.28.2" +tokio-stream = {version = "0.1.14", features = ["sync"]} # First party crates common_utils = { version = "0.1.0", path = "../common_utils", features = ["async_ext"] } diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index ca85d19d38b..ce2b138d923 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -383,6 +383,7 @@ impl super::RedisConnectionPool { ) -> CustomResult, errors::RedisError> { Ok(self .pool + .next() .hscan::<&str, &str>(key, pattern, count) .filter_map(|value| async move { match value { @@ -562,7 +563,7 @@ impl super::RedisConnectionPool { .await .into_report() .map_err(|err| match err.current_context().kind() { - RedisErrorKind::NotFound => { + RedisErrorKind::NotFound | RedisErrorKind::Parse => { err.change_context(errors::RedisError::StreamEmptyOrNotAvailable) } _ => err.change_context(errors::RedisError::StreamReadFailed), diff --git a/crates/redis_interface/src/lib.rs b/crates/redis_interface/src/lib.rs index bdc79560118..33d40ebe155 100644 --- a/crates/redis_interface/src/lib.rs +++ b/crates/redis_interface/src/lib.rs @@ -24,14 +24,14 @@ use std::sync::{atomic, Arc}; use common_utils::errors::CustomResult; use error_stack::{IntoReport, ResultExt}; -use fred::interfaces::ClientLike; pub use fred::interfaces::PubsubInterface; +use fred::{interfaces::ClientLike, prelude::EventInterface}; use router_env::logger; -pub use self::{commands::*, types::*}; +pub use self::types::*; pub struct RedisConnectionPool { - pub pool: fred::pool::RedisPool, + pub pool: fred::prelude::RedisPool, config: RedisConfig, pub subscriber: SubscriberClient, pub publisher: RedisClient, @@ -53,8 +53,10 @@ impl RedisClient { pub async fn new( config: fred::types::RedisConfig, reconnect_policy: fred::types::ReconnectPolicy, + perf: fred::types::PerformanceConfig, ) -> CustomResult { - let client = fred::prelude::RedisClient::new(config, None, Some(reconnect_policy)); + let client = + fred::prelude::RedisClient::new(config, Some(perf), None, Some(reconnect_policy)); client.connect(); client .wait_for_connect() @@ -73,8 +75,10 @@ impl SubscriberClient { pub async fn new( config: fred::types::RedisConfig, reconnect_policy: fred::types::ReconnectPolicy, + perf: fred::types::PerformanceConfig, ) -> CustomResult { - let client = fred::clients::SubscriberClient::new(config, None, Some(reconnect_policy)); + let client = + fred::clients::SubscriberClient::new(config, Some(perf), None, Some(reconnect_policy)); client.connect(); client .wait_for_connect() @@ -117,6 +121,17 @@ impl RedisConnectionPool { .into_report() .change_context(errors::RedisError::RedisConnectionError)?; + let perf = fred::types::PerformanceConfig { + auto_pipeline: conf.auto_pipeline, + default_command_timeout: std::time::Duration::from_secs(conf.default_command_timeout), + max_feed_count: conf.max_feed_count, + backpressure: fred::types::BackpressureConfig { + disable_auto_backpressure: conf.disable_auto_backpressure, + max_in_flight_commands: conf.max_in_flight_commands, + policy: fred::types::BackpressurePolicy::Drain, + }, + }; + if !conf.use_legacy_version { config.version = fred::types::RespVersion::RESP3; } @@ -127,13 +142,21 @@ impl RedisConnectionPool { conf.reconnect_delay, ); - let subscriber = SubscriberClient::new(config.clone(), reconnect_policy.clone()).await?; + let subscriber = + SubscriberClient::new(config.clone(), reconnect_policy.clone(), perf.clone()).await?; - let publisher = RedisClient::new(config.clone(), reconnect_policy.clone()).await?; + let publisher = + RedisClient::new(config.clone(), reconnect_policy.clone(), perf.clone()).await?; - let pool = fred::pool::RedisPool::new(config, None, Some(reconnect_policy), conf.pool_size) - .into_report() - .change_context(errors::RedisError::RedisConnectionError)?; + let pool = fred::prelude::RedisPool::new( + config, + Some(perf), + None, + Some(reconnect_policy), + conf.pool_size, + ) + .into_report() + .change_context(errors::RedisError::RedisConnectionError)?; pool.connect(); pool.wait_for_connect() @@ -153,16 +176,28 @@ impl RedisConnectionPool { } pub async fn on_error(&self, tx: tokio::sync::oneshot::Sender<()>) { - while let Ok(redis_error) = self.pool.on_error().recv().await { - logger::error!(?redis_error, "Redis protocol or connection error"); - logger::error!("current state: {:#?}", self.pool.state()); - if self.pool.state() == fred::types::ClientState::Disconnected { - if tx.send(()).is_err() { - logger::error!("The redis shutdown signal sender failed to signal"); + use futures::StreamExt; + use tokio_stream::wrappers::BroadcastStream; + + let error_rxs: Vec> = self + .pool + .clients() + .iter() + .map(|client| BroadcastStream::new(client.error_rx())) + .collect(); + + let mut error_rx = futures::stream::select_all(error_rxs); + loop { + if let Some(Ok(error)) = error_rx.next().await { + logger::error!(?error, "Redis protocol or connection error"); + if self.pool.state() == fred::types::ClientState::Disconnected { + if tx.send(()).is_err() { + logger::error!("The redis shutdown signal sender failed to signal"); + } + self.is_redis_available + .store(false, atomic::Ordering::SeqCst); + break; } - self.is_redis_available - .store(false, atomic::Ordering::SeqCst); - break; } } } diff --git a/crates/redis_interface/src/types.rs b/crates/redis_interface/src/types.rs index 5dc93070014..364fcfabc15 100644 --- a/crates/redis_interface/src/types.rs +++ b/crates/redis_interface/src/types.rs @@ -52,6 +52,11 @@ pub struct RedisSettings { /// TTL for hash-tables in seconds pub default_hash_ttl: u32, pub stream_read_count: u64, + pub auto_pipeline: bool, + pub disable_auto_backpressure: bool, + pub max_in_flight_commands: u64, + pub default_command_timeout: u64, + pub max_feed_count: u64, } impl RedisSettings { @@ -89,6 +94,11 @@ impl Default for RedisSettings { default_ttl: 300, stream_read_count: 1, default_hash_ttl: 900, + auto_pipeline: true, + disable_auto_backpressure: false, + max_in_flight_commands: 5000, + default_command_timeout: 0, + max_feed_count: 200, } } } diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index e498658e457..8897fdac2c2 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,14 +9,13 @@ readme = "README.md" license.workspace = true [features] -default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "profile_specific_fallback_routing", "retry", "frm"] +default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "business_profile_routing", "connector_choice_mca_id", "profile_specific_fallback_routing", "retry", "frm"] s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config", "olap"] frm = [] -basilisk = ["kms"] stripe = ["dep:serde_qs"] -release = ["kms", "stripe", "basilisk", "s3", "email", "business_profile_routing", "accounts_cache", "kv_store", "profile_specific_fallback_routing"] +release = ["kms", "stripe", "s3", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen", "recon"] olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap", "dep:analytics"] oltp = ["storage_impl/oltp"] kv_store = ["scheduler/kv_store"] @@ -31,6 +30,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect external_access_dc = ["dummy_connector"] detailed_errors = ["api_models/detailed_errors", "error-stack/serde"] payouts = [] +recon = ["email", "api_models/recon"] retry = [] [dependencies] @@ -79,12 +79,12 @@ ring = "0.16.20" roxmltree = "0.18.0" rust_decimal = { version = "1.30.0", features = ["serde-with-float", "serde-with-str"] } rustc-hash = "1.1.0" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" -serde_path_to_error = "0.1.11" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +serde_path_to_error = "0.1.14" serde_qs = { version = "0.12.0", optional = true } serde_urlencoded = "0.7.1" -serde_with = "3.0.0" +serde_with = "3.4.0" sha-1 = { version = "0.9" } sqlx = { version = "0.6.3", features = ["postgres", "runtime-actix", "runtime-actix-native-tls", "time", "bigdecimal"] } strum = { version = "0.25", features = ["derive"] } @@ -121,6 +121,7 @@ router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra scheduler = { version = "0.1.0", path = "../scheduler", default-features = false } storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } erased-serde = "0.3.31" +quick-xml = { version = "0.31.0", features = ["serialize"] } rdkafka = "0.36.0" [build-dependencies] @@ -134,7 +135,7 @@ rand = "0.8.5" serial_test = "2.0.0" time = { version = "0.3.21", features = ["macros"] } tokio = "1.28.2" -wiremock = "0.5" +wiremock = "0.5.18" # First party dev-dependencies test_utils = { version = "0.1.0", path = "../test_utils" } diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index f31e908e0dc..3f0febcc592 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -3,8 +3,9 @@ pub use analytics::*; pub mod routes { use actix_web::{web, Responder, Scope}; use analytics::{ - api_event::api_events_core, errors::AnalyticsError, lambda_utils::invoke_lambda, - sdk_events::sdk_events_core, + api_event::api_events_core, connector_events::connector_events_core, + errors::AnalyticsError, lambda_utils::invoke_lambda, + outgoing_webhook_event::outgoing_webhook_events_core, sdk_events::sdk_events_core, }; use api_models::analytics::{ GenerateReportRequest, GetApiEventFiltersRequest, GetApiEventMetricRequest, @@ -71,6 +72,14 @@ pub mod routes { ) .service(web::resource("api_event_logs").route(web::get().to(get_api_events))) .service(web::resource("sdk_event_logs").route(web::post().to(get_sdk_events))) + .service( + web::resource("connector_event_logs") + .route(web::get().to(get_connector_events)), + ) + .service( + web::resource("outgoing_webhook_event_logs") + .route(web::get().to(get_outgoing_webhook_events)), + ) .service( web::resource("filters/api_events") .route(web::post().to(get_api_event_filters)), @@ -314,6 +323,30 @@ pub mod routes { .await } + pub async fn get_outgoing_webhook_events( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Query< + api_models::analytics::outgoing_webhook_event::OutgoingWebhookLogsRequest, + >, + ) -> impl Responder { + let flow = AnalyticsFlow::GetOutgoingWebhookEvents; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: AuthenticationData, req| async move { + outgoing_webhook_events_core(&state.pool, req, auth.merchant_account.merchant_id) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth(Permission::Analytics), + api_locking::LockAction::NotApplicable, + )) + .await + } + pub async fn get_sdk_events( state: web::Data, req: actix_web::HttpRequest, @@ -557,4 +590,26 @@ pub mod routes { )) .await } + + pub async fn get_connector_events( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Query, + ) -> impl Responder { + let flow = AnalyticsFlow::GetConnectorEvents; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: AuthenticationData, req| async move { + connector_events_core(&state.pool, req, auth.merchant_account.merchant_id) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth(Permission::Analytics), + api_locking::LockAction::NotApplicable, + )) + .await + } } diff --git a/crates/router/src/bin/router.rs b/crates/router/src/bin/router.rs index beb2869f998..a02758d8edd 100644 --- a/crates/router/src/bin/router.rs +++ b/crates/router/src/bin/router.rs @@ -34,6 +34,9 @@ async fn main() -> ApplicationResult<()> { conf.validate() .expect("Failed to validate router configuration"); + #[cfg(feature = "vergen")] + println!("Starting router (Version: {})", router_env::git_tag!()); + let _guard = router_env::setup( &conf.log, router_env::service_name!(), diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index 32e9cfc6ca2..b800ecb897e 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -22,8 +22,6 @@ use tokio::sync::{mpsc, oneshot}; const SCHEDULER_FLOW: &str = "SCHEDULER_FLOW"; #[tokio::main] async fn main() -> CustomResult<(), ProcessTrackerError> { - // console_subscriber::init(); - let cmd_line = ::parse(); #[allow(clippy::expect_used)] @@ -58,6 +56,12 @@ async fn main() -> CustomResult<(), ProcessTrackerError> { let scheduler_flow = scheduler::SchedulerFlow::from_str(&scheduler_flow_str) .expect("Unable to parse SchedulerFlow from environment variable"); + #[cfg(feature = "vergen")] + println!( + "Starting {scheduler_flow} (Version: {})", + router_env::git_tag!() + ); + let _guard = router_env::setup( &state.conf.log, &scheduler_flow_str, diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index 03b59c55036..63205ea68ca 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -33,7 +33,7 @@ pub enum StripeErrorCode { expected_format: String, }, - #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_06", message = "Refund amount exceeds the payment amount.")] + #[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_06", message = "The refund amount exceeds the amount captured.")] RefundAmountExceedsPaymentAmount { param: String }, #[error(error_type = StripeErrorType::ApiError, code = "payment_intent_authentication_failure", message = "Payment failed while processing with connector. Retry payment.")] @@ -241,6 +241,8 @@ pub enum StripeErrorCode { LockTimeout, #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "Merchant connector account is configured with invalid {config}")] InvalidConnectorConfiguration { config: String }, + #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert currency to minor unit")] + CurrencyConversionFailed, // [#216]: https://github.com/juspay/hyperswitch/issues/216 // Implement the remaining stripe error codes @@ -518,6 +520,7 @@ impl From for StripeErrorCode { connector_name, }, errors::ApiErrorResponse::DuplicatePaymentMethod => Self::DuplicatePaymentMethod, + errors::ApiErrorResponse::PaymentBlocked => Self::PaymentFailed, errors::ApiErrorResponse::ClientSecretInvalid => Self::PaymentIntentInvalidParameter { param: "client_secret".to_owned(), }, @@ -595,6 +598,7 @@ impl From for StripeErrorCode { errors::ApiErrorResponse::InvalidConnectorConfiguration { config } => { Self::InvalidConnectorConfiguration { config } } + errors::ApiErrorResponse::CurrencyConversionFailed => Self::CurrencyConversionFailed, } } } @@ -662,7 +666,8 @@ impl actix_web::ResponseError for StripeErrorCode { | Self::CurrencyNotSupported { .. } | Self::DuplicateCustomer | Self::PaymentMethodUnactivated - | Self::InvalidConnectorConfiguration { .. } => StatusCode::BAD_REQUEST, + | Self::InvalidConnectorConfiguration { .. } + | Self::CurrencyConversionFailed => StatusCode::BAD_REQUEST, Self::RefundFailed | Self::PayoutFailed | Self::PaymentLinkNotFound diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 3c7d5f2918f..38007a3110d 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -118,7 +118,7 @@ impl From for payments::Card { card_number: card.number, card_exp_month: card.exp_month, card_exp_year: card.exp_year, - card_holder_name: card.holder_name.unwrap_or("name".to_string().into()), + card_holder_name: card.holder_name, card_cvc: card.cvc, card_issuer: None, card_network: None, diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 9d3f74af8cb..4c99d0cb00b 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -95,7 +95,7 @@ impl From for payments::Card { card_number: card.number, card_exp_month: card.exp_month, card_exp_year: card.exp_year, - card_holder_name: masking::Secret::new("stripe_cust".to_owned()), + card_holder_name: Some(masking::Secret::new("stripe_cust".to_owned())), card_cvc: card.cvc, card_issuer: None, card_network: None, diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs index c44e265a965..807278e0aff 100644 --- a/crates/router/src/compatibility/stripe/webhooks.rs +++ b/crates/router/src/compatibility/stripe/webhooks.rs @@ -183,6 +183,13 @@ fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static s api_models::enums::EventType::DisputeLost => "dispute.lost", api_models::enums::EventType::MandateActive => "mandate.active", api_models::enums::EventType::MandateRevoked => "mandate.revoked", + + // as per this doc https://stripe.com/docs/api/events/types#event_types-payment_intent.amount_capturable_updated + api_models::enums::EventType::PaymentAuthorized => { + "payment_intent.amount_capturable_updated" + } + // stripe treats partially captured payments as succeeded. + api_models::enums::EventType::PaymentCaptured => "payment_intent.succeeded", } } diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index 1ab156d32ad..d3ca0172f26 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -133,19 +133,34 @@ where .map_into_boxed_body() } - Ok(api::ApplicationResponse::PaymenkLinkForm(payment_link_data)) => { - match api::build_payment_link_html(*payment_link_data) { - Ok(rendered_html) => api::http_response_html_data(rendered_html), - Err(_) => api::http_response_err( - r#"{ - "error": { - "message": "Error while rendering payment link html page" - } - }"#, - ), + Ok(api::ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => { + match *boxed_payment_link_data { + api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => { + match api::build_payment_link_html(payment_link_data) { + Ok(rendered_html) => api::http_response_html_data(rendered_html), + Err(_) => api::http_response_err( + r#"{ + "error": { + "message": "Error while rendering payment link html page" + } + }"#, + ), + } + } + api::PaymentLinkAction::PaymentLinkStatus(payment_link_data) => { + match api::get_payment_link_status(payment_link_data) { + Ok(rendered_html) => api::http_response_html_data(rendered_html), + Err(_) => api::http_response_err( + r#"{ + "error": { + "message": "Error while rendering payment link status page" + } + }"#, + ), + } + } } } - Err(error) => api::log_and_return_error_response(error), }; diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 744d7883e95..42839bf3513 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -1101,6 +1101,89 @@ impl Default for super::settings::RequiredFields { ]), } ), + ( + enums::Connector::Helcim, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Iatapay, RequiredFieldFinal { @@ -1367,7 +1450,25 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserCardCvc, value: None, } - ) + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), ] ), common: HashMap::new(), @@ -3049,6 +3150,89 @@ impl Default for super::settings::RequiredFields { ]), } ), + ( + enums::Connector::Helcim, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Iatapay, RequiredFieldFinal { @@ -3315,7 +3499,25 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserCardCvc, value: None, } - ) + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), ] ), common: HashMap::new(), @@ -4216,7 +4418,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), - common:HashMap::from([ + common: HashMap::from([ ( "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), RequiredFieldInfo { @@ -4279,43 +4481,521 @@ impl Default for super::settings::RequiredFields { ConnectorFields { fields: HashMap::from([ ( - enums::Connector::Stripe, + enums::Connector::Aci, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.ideal.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), common: HashMap::new(), } ), - ]), - }, - ), - ( - enums::PaymentMethodType::Sofort, - ConnectorFields { - fields: HashMap::from([ ( - enums::Connector::Stripe, + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Globalpay, RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), common: HashMap::new(), } ), - ]), - }, - ), - ( - enums::PaymentMethodType::Eps, - ConnectorFields { - fields: HashMap::from([ ( - enums::Connector::Stripe, + enums::Connector::Mollie, RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), common: HashMap::new(), } - ) + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.ideal.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + display_name: "billing_email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Sofort, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Eps, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.eps.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.eps.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + display_name: "bank_account_country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]) + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.eps.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + display_name: "bank_account_country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + )] + ), + common: HashMap::new(), + } + ), ]), }, ), @@ -4541,6 +5221,93 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } ) ]), }, @@ -4643,6 +5410,93 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } ) ]), }, diff --git a/crates/router/src/configs/kms.rs b/crates/router/src/configs/kms.rs index bf6ee44d28b..4e236a512ac 100644 --- a/crates/router/src/configs/kms.rs +++ b/crates/router/src/configs/kms.rs @@ -13,19 +13,11 @@ impl KmsDecrypt for settings::Jwekey { kms_client: &KmsClient, ) -> CustomResult { ( - self.locker_encryption_key1, - self.locker_encryption_key2, - self.locker_decryption_key1, - self.locker_decryption_key2, self.vault_encryption_key, self.rust_locker_encryption_key, self.vault_private_key, self.tunnel_private_key, ) = tokio::try_join!( - kms_client.decrypt(self.locker_encryption_key1), - kms_client.decrypt(self.locker_encryption_key2), - kms_client.decrypt(self.locker_decryption_key1), - kms_client.decrypt(self.locker_decryption_key2), kms_client.decrypt(self.vault_encryption_key), kms_client.decrypt(self.rust_locker_encryption_key), kms_client.decrypt(self.vault_private_key), diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 1c885e90cc7..3d93c2f188b 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -287,6 +287,15 @@ pub struct PaymentMethodTokenFilter { pub payment_method: HashSet, pub payment_method_type: Option, pub long_lived_token: bool, + pub apple_pay_pre_decrypt_flow: Option, +} + +#[derive(Debug, Deserialize, Clone, Default)] +#[serde(deny_unknown_fields, rename_all = "snake_case")] +pub enum ApplePayPreDecryptFlow { + #[default] + ConnectorTokenization, + NetworkTokenization, } #[derive(Debug, Deserialize, Clone, Default)] @@ -499,12 +508,6 @@ pub struct EphemeralConfig { #[derive(Debug, Deserialize, Clone, Default)] #[serde(default)] pub struct Jwekey { - pub locker_key_identifier1: String, - pub locker_key_identifier2: String, - pub locker_encryption_key1: String, - pub locker_encryption_key2: String, - pub locker_decryption_key1: String, - pub locker_decryption_key2: String, pub vault_encryption_key: String, pub rust_locker_encryption_key: String, pub vault_private_key: String, @@ -556,7 +559,7 @@ impl From for storage_impl::config::Database { dbname: val.dbname, pool_size: val.pool_size, connection_timeout: val.connection_timeout, - queue_strategy: val.queue_strategy.into(), + queue_strategy: val.queue_strategy, min_idle: val.min_idle, max_lifetime: val.max_lifetime, } @@ -614,9 +617,11 @@ pub struct Connectors { pub payme: ConnectorParams, pub paypal: ConnectorParams, pub payu: ConnectorParams, + pub placetopay: ConnectorParams, pub powertranz: ConnectorParams, pub prophetpay: ConnectorParams, pub rapyd: ConnectorParams, + pub riskified: ConnectorParams, pub shift4: ConnectorParams, pub signifyd: ConnectorParams, pub square: ConnectorParams, @@ -895,11 +900,11 @@ impl<'de> Deserialize<'de> for LockSettings { redis_lock_expiry_seconds, delay_between_retries_in_milliseconds, } = Inner::deserialize(deserializer)?; - let redis_lock_expiry_seconds = redis_lock_expiry_seconds * 1000; + let redis_lock_expiry_milliseconds = redis_lock_expiry_seconds * 1000; Ok(Self { redis_lock_expiry_seconds, delay_between_retries_in_milliseconds, - lock_retries: redis_lock_expiry_seconds / delay_between_retries_in_milliseconds, + lock_retries: redis_lock_expiry_milliseconds / delay_between_retries_in_milliseconds, }) } } diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 55c61442591..de6e250842c 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -36,9 +36,11 @@ pub mod payeezy; pub mod payme; pub mod paypal; pub mod payu; +pub mod placetopay; pub mod powertranz; pub mod prophetpay; pub mod rapyd; +pub mod riskified; pub mod shift4; pub mod signifyd; pub mod square; @@ -63,8 +65,9 @@ pub use self::{ globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, - payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, powertranz::Powertranz, - prophetpay::Prophetpay, rapyd::Rapyd, shift4::Shift4, signifyd::Signifyd, square::Square, - stax::Stax, stripe::Stripe, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, - worldline::Worldline, worldpay::Worldpay, zen::Zen, + payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, placetopay::Placetopay, + powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, + shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, + trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, + worldpay::Worldpay, zen::Zen, }; diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index f6384bf0a5c..bbb88209b27 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -2,6 +2,7 @@ mod result_codes; pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as aci; @@ -20,7 +21,7 @@ use crate::{ self, api::{self, ConnectorCommon}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -136,6 +137,17 @@ impl > for Aci { // Issue: #173 + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Aci".to_string()).into()) + } } impl @@ -202,9 +214,6 @@ impl .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( - self, req, connectors, - )?) .build(), )) } @@ -284,7 +293,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { // encode only for for urlencoded things. let connector_router_data = aci::AciRouterData::try_from(( &self.get_currency_unit(), @@ -293,13 +302,8 @@ impl req, ))?; let connector_req = aci::AciPaymentsRequest::try_from(&connector_router_data)?; - let aci_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(aci_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -321,7 +325,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -393,14 +397,9 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = aci::AciCancelRequest::try_from(req)?; - let aci_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(aci_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( &self, @@ -413,7 +412,7 @@ impl .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .body(types::PaymentsVoidType::get_request_body( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(), @@ -489,7 +488,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = aci::AciRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -497,12 +496,7 @@ impl services::ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(body)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -518,7 +512,7 @@ impl services::ConnectorIntegration Result { let (item, bank_redirect_data) = value; let payment_data = match bank_redirect_data { - api_models::payments::BankRedirectData::Eps { .. } => { + api_models::payments::BankRedirectData::Eps { country, .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Eps, - bank_account_country: Some(api_models::enums::CountryAlpha2::AT), + bank_account_country: Some(country.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "eps.country", + }, + )?), bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, @@ -174,19 +178,27 @@ impl merchant_transaction_id: None, customer_email: None, })), - api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { - Self::BankRedirect(Box::new(BankRedirectionPMData { - payment_brand: PaymentBrand::Ideal, - bank_account_country: Some(api_models::enums::CountryAlpha2::NL), - bank_account_bank_name: bank_name.to_owned(), - bank_account_bic: None, - bank_account_iban: None, - billing_country: None, - merchant_customer_id: None, - merchant_transaction_id: None, - customer_email: None, - })) - } + api_models::payments::BankRedirectData::Ideal { + bank_name, country, .. + } => Self::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Ideal, + bank_account_country: Some(country.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "ideal.country", + }, + )?), + bank_account_bank_name: Some(bank_name.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "ideal.bank_name", + }, + )?), + bank_account_bic: None, + bank_account_iban: None, + billing_country: None, + merchant_customer_id: None, + merchant_transaction_id: None, + customer_email: None, + })), api_models::payments::BankRedirectData::Sofort { country, .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Sofortueberweisung, @@ -254,7 +266,9 @@ impl TryFrom for PaymentDetails { fn try_from(card_data: api_models::payments::Card) -> Result { Ok(Self::AciCard(Box::new(CardDetails { card_number: card_data.card_number, - card_holder: card_data.card_holder_name, + card_holder: card_data + .card_holder_name + .unwrap_or(Secret::new("".to_string())), card_expiry_month: card_data.card_exp_month, card_expiry_year: card_data.card_exp_year, card_cvv: card_data.card_cvc, @@ -410,10 +424,9 @@ impl TryFrom<&AciRouterData<&types::PaymentsAuthorizeRouterData>> for AciPayment | api::PaymentMethodData::CardRedirect(_) | api::PaymentMethodData::Upi(_) | api::PaymentMethodData::Voucher(_) - | api::PaymentMethodData::CardToken(_) => Err(errors::ConnectorError::NotSupported { - message: format!("{:?}", item.router_data.payment_method), - connector: "Aci", - })?, + | api::PaymentMethodData::CardToken(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Aci"), + ))?, } } } diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index ddd93bc289a..32f14fd0210 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -4,15 +4,16 @@ use std::fmt::Debug; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::{enums as storage_enums, enums}; use error_stack::{IntoReport, ResultExt}; use ring::hmac; use router_env::{instrument, tracing}; use self::transformers as adyen; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::utils as connector_utils, consts, core::errors::{self, CustomResult}, headers, logger, @@ -27,7 +28,7 @@ use crate::{ domain, transformers::ForeignFrom, }, - utils::{self, crypto, ByteSliceExt, BytesExt, OptionExt}, + utils::{crypto, ByteSliceExt, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -171,7 +172,7 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let authorize_req = types::PaymentsAuthorizeRouterData::from(( req, types::PaymentsAuthorizeData::from(req), @@ -184,12 +185,7 @@ impl ))?; let connector_req = adyen::AdyenPaymentRequest::try_from(&connector_router_data)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -202,7 +198,7 @@ impl .url(&types::SetupMandateType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -309,7 +305,7 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -317,12 +313,7 @@ impl req, ))?; let connector_req = adyen::AdyenCaptureRequest::try_from(&connector_router_data)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -337,7 +328,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -405,71 +396,48 @@ impl &self, req: &types::RouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - // Adyen doesn't support PSync flow. We use PSync flow to fetch payment details, - // specifically the redirect URL that takes the user to their Payment page. In non-redirection flows, - // we rely on webhooks to obtain the payment status since there is no encoded data available. - // encoded_data only includes the redirect URL and is only relevant in redirection flows. - let encoded_value = req + ) -> CustomResult { + let encoded_data = req .request .encoded_data .clone() - .get_required_value("encoded_data"); - - match encoded_value { - Ok(encoded_data) => { - let adyen_redirection_type = serde_urlencoded::from_str::< - transformers::AdyenRedirectRequestTypes, - >(encoded_data.as_str()) - .into_report() - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - let redirection_request = match adyen_redirection_type { - adyen::AdyenRedirectRequestTypes::AdyenRedirection(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenRedirection( - adyen::AdyenRedirection { - redirect_result: req.redirect_result, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - adyen::AdyenRedirectRequestTypes::AdyenThreeDS(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenThreeDS( - adyen::AdyenThreeDS { - three_ds_result: req.three_ds_result, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - adyen::AdyenRedirectRequestTypes::AdyenRefusal(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenRefusal( - adyen::AdyenRefusal { - payload: req.payload, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - }; - - let adyen_request = types::RequestBody::log_and_get_request_body( - &redirection_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - Ok(Some(adyen_request)) + .get_required_value("encoded_data") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let adyen_redirection_type = serde_urlencoded::from_str::< + transformers::AdyenRedirectRequestTypes, + >(encoded_data.as_str()) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + let connector_req = match adyen_redirection_type { + adyen::AdyenRedirectRequestTypes::AdyenRedirection(req) => { + adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenRedirection( + adyen::AdyenRedirection { + redirect_result: req.redirect_result, + type_of_redirection_result: None, + result_code: None, + }, + ), + } } - Err(_) => Ok(None), - } + adyen::AdyenRedirectRequestTypes::AdyenThreeDS(req) => adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenThreeDS(adyen::AdyenThreeDS { + three_ds_result: req.three_ds_result, + type_of_redirection_result: None, + result_code: None, + }), + }, + adyen::AdyenRedirectRequestTypes::AdyenRefusal(req) => adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenRefusal(adyen::AdyenRefusal { + payload: req.payload, + type_of_redirection_result: None, + result_code: None, + }), + }, + }; + + Ok(RequestContent::Json(Box::new(connector_req))) } fn get_url( @@ -489,20 +457,30 @@ impl req: &types::RouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let request_body = self.get_request_body(req, connectors)?; - match request_body { - Some(_) => Ok(Some( + // Adyen doesn't support PSync flow. We use PSync flow to fetch payment details, + // specifically the redirect URL that takes the user to their Payment page. In non-redirection flows, + // we rely on webhooks to obtain the payment status since there is no encoded data available. + // encoded_data only includes the redirect URL and is only relevant in redirection flows. + if req + .request + .encoded_data + .clone() + .get_required_value("encoded_data") + .is_ok() + { + Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( + .set_body(types::PaymentsSyncType::get_request_body( self, req, connectors, )?) .build(), - )), - None => Ok(None), + )) + } else { + Ok(None) } } @@ -599,7 +577,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -607,12 +585,7 @@ impl req, ))?; let connector_req = adyen::AdyenPaymentRequest::try_from(&connector_router_data)?; - let request_body = types::RequestBody::log_and_get_request_body( - &connector_req, - common_utils::ext_traits::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request_body)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -630,7 +603,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -717,15 +690,10 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenBalanceRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -743,7 +711,7 @@ impl .headers(types::PaymentsPreProcessingType::get_headers( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -843,15 +811,10 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenCancelRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -864,7 +827,7 @@ impl .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .body(types::PaymentsVoidType::get_request_body( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(), @@ -957,14 +920,9 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenPayoutCancelRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -977,7 +935,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1053,12 +1011,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1071,7 +1024,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1148,12 +1101,7 @@ impl req, ))?; let connector_req = adyen::AdyenPayoutEligibilityRequest::try_from(&connector_router_data)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1170,7 +1118,7 @@ impl .headers(types::PayoutEligibilityType::get_headers( self, req, connectors, )?) - .body(types::PayoutEligibilityType::get_request_body( + .set_body(types::PayoutEligibilityType::get_request_body( self, req, connectors, )?) .build(); @@ -1252,7 +1200,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1260,12 +1208,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1280,7 +1223,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -1362,12 +1305,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1383,7 +1321,7 @@ impl services::ConnectorIntegration Option { - match payout_method_data { - PayoutMethodData::Card(card) => Some(PayoutCardDetails { - _type: "scheme".to_string(), // FIXME: Remove hardcoding - number: card.card_number.peek().to_string(), - expiry_month: card.expiry_month.peek().to_string(), - expiry_year: card.expiry_year.peek().to_string(), - holder_name: card.card_holder_name.peek().to_string(), - }), - _ => None, - } -} - fn get_social_security_number( voucher_data: &api_models::payments::VoucherData, ) -> Option> { @@ -2164,10 +2148,14 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod api_models::payments::BankRedirectData::Eps { bank_name, .. } => Ok( AdyenPaymentMethod::Eps(Box::new(BankRedirectionWithIssuer { payment_type: PaymentType::Eps, - issuer: bank_name - .map(|bank_name| AdyenTestBankNames::try_from(&bank_name)) - .transpose()? - .map(|adyen_bank_name| adyen_bank_name.0), + issuer: Some( + AdyenTestBankNames::try_from(&bank_name.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "eps.bank_name", + }, + )?)? + .0, + ), })), ), api_models::payments::BankRedirectData::Giropay { .. } => Ok( @@ -2178,10 +2166,14 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod api_models::payments::BankRedirectData::Ideal { bank_name, .. } => Ok( AdyenPaymentMethod::Ideal(Box::new(BankRedirectionWithIssuer { payment_type: PaymentType::Ideal, - issuer: bank_name - .map(|bank_name| AdyenTestBankNames::try_from(&bank_name)) - .transpose()? - .map(|adyen_bank_name| adyen_bank_name.0), + issuer: Some( + AdyenTestBankNames::try_from(&bank_name.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "ideal.bank_name", + }, + )?)? + .0, + ), })), ), api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { issuer } => { @@ -3972,12 +3964,12 @@ pub struct AdyenPayoutCreateRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct PayoutBankDetails { - bank_name: String, + iban: Secret, + owner_name: Secret, + bank_city: Option, + bank_name: Option, bic: Option>, - country_code: storage_enums::CountryAlpha2, - iban: Option>, - owner_name: Option>, - bank_city: String, + country_code: Option, tax_id: Option>, } @@ -4028,11 +4020,11 @@ pub struct AdyenPayoutEligibilityRequest { #[serde(rename_all = "camelCase")] pub struct PayoutCardDetails { #[serde(rename = "type")] - _type: String, - number: String, - expiry_month: String, - expiry_year: String, - holder_name: String, + payment_method_type: String, + number: CardNumber, + expiry_month: Secret, + expiry_year: Secret, + holder_name: Secret, } #[cfg(feature = "payouts")] @@ -4087,6 +4079,31 @@ pub struct AdyenPayoutCancelRequest { merchant_account: Secret, } +#[cfg(feature = "payouts")] +impl TryFrom<&PayoutMethodData> for PayoutCardDetails { + type Error = Error; + fn try_from(item: &PayoutMethodData) -> Result { + match item { + PayoutMethodData::Card(card) => Ok(Self { + payment_method_type: "scheme".to_string(), // FIXME: Remove hardcoding + number: card.card_number.clone(), + expiry_month: card.expiry_month.clone(), + expiry_year: card.expiry_year.clone(), + holder_name: card + .card_holder_name + .clone() + .get_required_value("card_holder_name") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.card.holder_name", + })?, + }), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.card", + })?, + } + } +} + // Payouts eligibility request transform #[cfg(feature = "payouts")] impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutEligibilityRequest { @@ -4094,12 +4111,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutE fn try_from(item: &AdyenRouterData<&types::PayoutsRouterData>) -> Result { let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; let payout_method_data = - get_payout_card_details(&item.router_data.get_payout_method_data()?).map_or( - Err(errors::ConnectorError::MissingRequiredField { - field_name: "payout_method_data", - }), - Ok, - )?; + PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?; Ok(Self { amount: Amount { currency: item.router_data.request.destination_currency, @@ -4147,6 +4159,11 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC .customer_details .to_owned() .map_or((None, None), |c| (c.name, c.email)); + let owner_name = owner_name.get_required_value("owner_name").change_context( + errors::ConnectorError::MissingRequiredField { + field_name: "payout_method_data.bank.owner_name", + }, + )?; match item.router_data.get_payout_method_data()? { PayoutMethodData::Card(_) => Err(errors::ConnectorError::NotSupported { @@ -4161,7 +4178,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC bank_city: b.bank_city, owner_name, bic: b.bic, - iban: Some(b.iban), + iban: b.iban, tax_id: None, }, payouts::BankPayout::Ach(..) => Err(errors::ConnectorError::NotSupported { @@ -4226,13 +4243,7 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutF value: item.amount.to_owned(), currency: item.router_data.request.destination_currency, }, - card: get_payout_card_details(&item.router_data.get_payout_method_data()?) - .map_or( - Err(errors::ConnectorError::MissingRequiredField { - field_name: "payout_method_data", - }), - Ok, - )?, + card: PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?, billing_address: get_address_info(item.router_data.get_billing().ok()), merchant_account, reference: item.router_data.request.payout_id.clone(), diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs index 67e20a951d1..19d69b688c9 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/router/src/connector/airwallex.rs @@ -2,16 +2,18 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::ext_traits::{ByteSliceExt, ValueExt}; +use common_utils::{ + ext_traits::{ByteSliceExt, ValueExt}, + request::RequestContent, +}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as airwallex; -use super::utils::{AccessTokenRequestInfo, RefundsRequestData}; +use super::utils::{self as connector_utils, AccessTokenRequestInfo, RefundsRequestData}; use crate::{ configs::settings, - connector::utils as connector_utils, core::{ errors::{self, CustomResult}, payments, @@ -27,7 +29,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, RouterData, }, - utils::{self, crypto, BytesExt}, + utils::{crypto, BytesExt}, }; #[derive(Debug, Clone)] @@ -124,6 +126,16 @@ impl types::PaymentsResponseData, > for Airwallex { + fn build_request( + &self, + _req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Airwallex".to_string()) + .into(), + ) + } } impl api::PaymentToken for Airwallex {} @@ -185,9 +197,6 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = airwallex::AirwallexIntentRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -278,7 +282,7 @@ impl .url(&types::PaymentsInitType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) - .body(types::PaymentsInitType::get_request_body( + .set_body(types::PaymentsInitType::get_request_body( self, req, connectors, )?) .build(), @@ -380,7 +384,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -388,12 +392,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -411,7 +410,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( &self, @@ -581,7 +575,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -646,16 +640,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?; - let airwallex_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -671,7 +659,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = airwallex::AirwallexPaymentsCancelRequest::try_from(req)?; - let airwallex_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( &self, @@ -787,7 +770,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -845,12 +828,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -865,7 +843,7 @@ impl ConnectorIntegration for Authorizedotnet { // Issue: #173 + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented( + "Setup Mandate flow for Authorizedotnet".to_string(), + ) + .into()) + } } impl ConnectorIntegration @@ -146,7 +160,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -156,12 +170,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -177,7 +186,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = authorizedotnet::AuthorizedotnetCreateSyncRequest::try_from(req)?; - let sync_request = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(sync_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -265,7 +269,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -342,12 +346,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -369,7 +368,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = authorizedotnet::CancelOrCaptureTransactionRequest::try_from(req)?; - let authorizedotnet_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -455,7 +449,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -535,12 +529,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -555,7 +544,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -632,12 +621,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(sync_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -650,7 +634,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -730,12 +714,7 @@ impl let connector_req = authorizedotnet::PaypalConfirmRequest::try_from(&connector_router_data)?; - let authorizedotnet_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -753,7 +732,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -924,7 +903,12 @@ fn get_error_response( })), Some(authorizedotnet::TransactionResponse::AuthorizedotnetTransactionResponseError(_)) | None => { - let message = &response.messages.message[0].text; + let message = &response + .messages + .message + .first() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .text; Ok(types::ErrorResponse { code: consts::NO_ERROR_CODE.to_string(), message: message.to_string(), diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 30323ca4ef2..96cf3b6ffc5 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -619,7 +619,7 @@ impl Some(TransactionResponse::AuthorizedotnetTransactionResponseError(_)) | None => { Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(get_err_response(item.http_code, item.response.messages)), + response: Err(get_err_response(item.http_code, item.response.messages)?), ..item.data }) } @@ -689,7 +689,7 @@ impl } None => Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(get_err_response(item.http_code, item.response.messages)), + response: Err(get_err_response(item.http_code, item.response.messages)?), ..item.data }), } @@ -944,7 +944,7 @@ impl TryFrom Ok(Self { - response: Err(get_err_response(item.http_code, item.response.messages)), + response: Err(get_err_response(item.http_code, item.response.messages)?), ..item.data }), } @@ -986,7 +986,7 @@ impl }) } None => Ok(Self { - response: Err(get_err_response(item.http_code, item.response.messages)), + response: Err(get_err_response(item.http_code, item.response.messages)?), ..item.data }), } @@ -1024,15 +1024,22 @@ impl From> for TransactionType { } } -fn get_err_response(status_code: u16, message: ResponseMessages) -> types::ErrorResponse { - types::ErrorResponse { - code: message.message[0].code.clone(), - message: message.message[0].text.clone(), +fn get_err_response( + status_code: u16, + message: ResponseMessages, +) -> Result { + let response_message = message + .message + .first() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(types::ErrorResponse { + code: response_message.code.clone(), + message: response_message.text.clone(), reason: None, status_code, attempt_status: None, connector_transaction_id: None, - } + }) } #[derive(Debug, Deserialize)] diff --git a/crates/router/src/connector/bambora.rs b/crates/router/src/connector/bambora.rs index 19849763ed8..37c6ae6cd6b 100644 --- a/crates/router/src/connector/bambora.rs +++ b/crates/router/src/connector/bambora.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as bambora; @@ -28,7 +29,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -138,6 +139,20 @@ impl types::PaymentsResponseData, > for Bambora { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Bambora".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Bambora {} @@ -175,15 +190,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = bambora::BamboraPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -197,7 +207,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bambora::BamboraPaymentsCaptureRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -371,7 +376,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = bambora::BamboraPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -468,7 +468,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bambora::BamboraRefundRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -564,7 +559,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; + ) -> CustomResult { + let connector_req = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -777,7 +764,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(); diff --git a/crates/router/src/connector/bambora/transformers.rs b/crates/router/src/connector/bambora/transformers.rs index 2d50569f9a4..8f18ca272dd 100644 --- a/crates/router/src/connector/bambora/transformers.rs +++ b/crates/router/src/connector/bambora/transformers.rs @@ -117,7 +117,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BamboraPaymentsRequest { enums::AuthenticationType::NoThreeDs => None, }; let bambora_card = BamboraCard { - name: req_card.card_holder_name, + name: req_card + .card_holder_name + .unwrap_or(Secret::new("".to_string())), number: req_card.card_number, expiry_month: req_card.card_exp_month, expiry_year: req_card.card_exp_year, diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index a01ea72338c..0d901b99078 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -3,6 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -11,6 +12,7 @@ use time::OffsetDateTime; use transformers as bankofamerica; use url::Url; +use super::utils::{PaymentsAuthorizeRequestData, RouterData}; use crate::{ configs::settings, connector::{utils as connector_utils, utils::RefundsRequestData}, @@ -27,7 +29,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; pub const V_C_MERCHANT_ID: &str = "v-c-merchant-id"; @@ -47,6 +49,8 @@ impl api::Refund for Bankofamerica {} impl api::RefundExecute for Bankofamerica {} impl api::RefundSync for Bankofamerica {} impl api::PaymentToken for Bankofamerica {} +impl api::PaymentsPreProcessing for Bankofamerica {} +impl api::PaymentsCompleteAuthorize for Bankofamerica {} impl Bankofamerica { pub fn generate_digest(&self, payload: &[u8]) -> String { @@ -134,10 +138,8 @@ where .skip(base_url.len() - 1) .collect(); let sha256 = self.generate_digest( - boa_req - .map_or("{}".to_string(), |s| { - types::RequestBody::get_inner_value(s).expose() - }) + types::RequestBody::get_inner_value(boa_req) + .expose() .as_bytes(), ); let signature = self.generate_signature( @@ -204,37 +206,50 @@ impl ConnectorCommon for Bankofamerica { } else { consts::NO_ERROR_MESSAGE }; - - let (code, message) = match response.error_information { - Some(ref error_info) => (error_info.reason.clone(), error_info.message.clone()), - None => ( - response - .reason - .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { - reason.to_string() - }), - response - .message - .map_or(error_message.to_string(), |message| message), - ), - }; - let connector_reason = match response.details { - Some(details) => details - .iter() - .map(|det| format!("{} : {}", det.field, det.reason)) - .collect::>() - .join(", "), - None => message.clone(), - }; - - Ok(ErrorResponse { - status_code: res.status_code, - code, - message, - reason: Some(connector_reason), - attempt_status: None, - connector_transaction_id: None, - }) + match response { + transformers::BankOfAmericaErrorResponse::StandardError(response) => { + let (code, connector_reason) = match response.error_information { + Some(ref error_info) => (error_info.reason.clone(), error_info.message.clone()), + None => ( + response + .reason + .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { + reason.to_string() + }), + response + .message + .map_or(error_message.to_string(), |message| message), + ), + }; + let message = match response.details { + Some(details) => details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", "), + None => connector_reason.clone(), + }; + + Ok(ErrorResponse { + status_code: res.status_code, + code, + message, + reason: Some(connector_reason), + attempt_status: None, + connector_transaction_id: None, + }) + } + transformers::BankOfAmericaErrorResponse::AuthenticationError(response) => { + Ok(ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: response.response.rmsg.clone(), + reason: Some(response.response.rmsg), + attempt_status: None, + connector_transaction_id: None, + }) + } + } } } @@ -271,6 +286,127 @@ impl types::PaymentsResponseData, > for Bankofamerica { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented( + "Setup Mandate flow for Bankofamerica".to_string(), + ) + .into()) + } +} + +impl + ConnectorIntegration< + api::PreProcessing, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + > for Bankofamerica +{ + fn get_headers( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let redirect_response = req.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => Ok(format!( + "{}risk/v1/authentications", + self.base_url(connectors) + )), + Some(_) | None => Ok(format!( + "{}risk/v1/authentication-results", + self.base_url(connectors) + )), + } + } + fn get_request_body( + &self, + req: &types::PaymentsPreProcessingRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?, + req, + ))?; + let connector_req = + bankofamerica::BankOfAmericaPreProcessingRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsPreProcessingRouterData, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaPreProcessingResponse = res + .response + .parse_struct("BankOfAmerica AuthEnrollmentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } } impl ConnectorIntegration @@ -290,34 +426,39 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}pts/v2/payments/", - api::ConnectorCommon::base_url(self, connectors) - )) + if req.is_three_ds() && req.request.is_card() { + Ok(format!( + "{}risk/v1/authentication-setups", + self.base_url(connectors) + )) + } else { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = - bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?; - let bankofamerica_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_payments_request)) + if req.is_three_ds() && req.request.is_card() { + let connector_req = + bankofamerica::BankOfAmericaAuthSetupRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } else { + let connector_req = + bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } } fn build_request( @@ -335,7 +476,7 @@ impl ConnectorIntegration CustomResult { + if data.is_three_ds() && data.request.is_card() { + let response: bankofamerica::BankOfAmericaAuthSetupResponse = res + .response + .parse_struct("Bankofamerica AuthSetupResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } else { + let response: bankofamerica::BankOfAmericaPaymentsResponse = res + .response + .parse_struct("Bankofamerica PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } + + fn get_5xx_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res + .response + .parse_struct("BankOfAmericaServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } +} + +impl + ConnectorIntegration< + api::CompleteAuthorize, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + > for Bankofamerica +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}pts/v2/payments/", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = + bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: Response, + ) -> CustomResult { let response: bankofamerica::BankOfAmericaPaymentsResponse = res .response .parse_struct("BankOfAmerica PaymentResponse") @@ -364,6 +629,33 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res + .response + .parse_struct("BankOfAmericaServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } } impl ConnectorIntegration @@ -471,21 +763,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = + let connector_req = bankofamerica::BankOfAmericaCaptureRequest::try_from(&connector_router_data)?; - let bankofamerica_capture_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_capture_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -501,7 +788,7 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res + .response + .parse_struct("BankOfAmericaServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } } impl ConnectorIntegration @@ -563,7 +871,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request @@ -578,15 +886,10 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_void_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -600,7 +903,7 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: bankofamerica::BankOfAmericaServerErrorResponse = res + .response + .parse_struct("BankOfAmericaServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } } impl ConnectorIntegration @@ -662,20 +986,16 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = bankofamerica::BankOfAmericaRefundRequest::try_from(&connector_router_data)?; - let bankofamerica_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_req)) + let connector_req = + bankofamerica::BankOfAmericaRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -690,7 +1010,7 @@ impl ConnectorIntegration, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProcessingInformation { - capture: bool, + capture: Option, payment_solution: Option, + commerce_indicator: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantDefinedInformation { + key: u8, + value: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + directory_server_transaction_id: Option, + specification_version: Option, } #[derive(Debug, Serialize)] @@ -111,6 +138,19 @@ pub struct GooglePayPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenizedCard { + transaction_type: TransactionType, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenPaymentInformation { + fluid_data: FluidData, + tokenized_card: ApplePayTokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApplePayPaymentInformation { @@ -123,6 +163,7 @@ pub enum PaymentInformation { Cards(CardPaymentInformation), GooglePay(GooglePayPaymentInformation), ApplePay(ApplePayPaymentInformation), + ApplePayToken(ApplePayTokenPaymentInformation), } #[derive(Debug, Serialize)] @@ -188,12 +229,14 @@ fn build_bill_to( .address .as_ref() .ok_or_else(utils::missing_field_err("billing.address"))?; + let mut state = address.to_state_code()?.peek().clone(); + state.truncate(20); Ok(BillTo { first_name: address.get_first_name()?.to_owned(), last_name: address.get_last_name()?.to_owned(), address1: address.get_line1()?.to_owned(), locality: address.get_city()?.to_owned(), - administrative_area: address.to_state_code()?, + administrative_area: Secret::from(state), postal_code: address.get_zip()?.to_owned(), country: address.get_country()?.to_owned(), email, @@ -261,6 +304,28 @@ impl } } +impl + From<( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to, + } + } +} + impl From<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -274,11 +339,40 @@ impl ), ) -> Self { Self { - capture: matches!( + capture: Some(matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None - ), + )), + payment_solution: solution.map(String::from), + commerce_indicator: String::from("internet"), + } + } +} + +impl + From<( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &BankOfAmericaConsumerAuthValidateResponse, + )> for ProcessingInformation +{ + fn from( + (item, solution, three_ds_data): ( + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &BankOfAmericaConsumerAuthValidateResponse, + ), + ) -> Self { + Self { + capture: Some(matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + )), payment_solution: solution.map(String::from), + commerce_indicator: three_ds_data + .indicator + .to_owned() + .unwrap_or(String::from("internet")), } } } @@ -293,22 +387,74 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } -#[derive(Debug, Deserialize, Serialize)] +impl From<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for ClientReferenceInformation +{ + fn from(item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } +} + +impl ForeignFrom for Vec { + fn foreign_from(metadata: Value) -> Self { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()) + .unwrap_or(std::collections::BTreeMap::new()); + let mut vector: Self = Self::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; + } + vector + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientReferenceInformation { code: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientProcessorInformation { + avs: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientRiskInformation { + rules: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ClientRiskInformationRules { + name: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Avs { + code: String, + code_raw: String, +} + impl TryFrom<( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, payments::Card, )> for BankOfAmericaPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, ccard): ( - &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, payments::Card, ), ) -> Result { @@ -331,15 +477,95 @@ impl card_type, }, }); + let client_reference_information = ClientReferenceInformation::from(item); + + let three_ds_info: BankOfAmericaThreeDSMetadata = item + .router_data + .request + .connector_meta + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "connector_meta", + })? + .parse_value("BankOfAmericaThreeDSMetadata") + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "Merchant connector account metadata", + })?; + + let processing_information = + ProcessingInformation::from((item, None, &three_ds_info.three_ds_data)); + + let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation { + ucaf_collection_indicator: three_ds_info.three_ds_data.ucaf_collection_indicator, + cavv: three_ds_info.three_ds_data.cavv, + ucaf_authentication_data: three_ds_info.three_ds_data.ucaf_authentication_data, + xid: three_ds_info.three_ds_data.xid, + directory_server_transaction_id: three_ds_info + .three_ds_data + .directory_server_transaction_id, + specification_version: three_ds_info.three_ds_data.specification_version, + }); + + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information, + merchant_defined_information, + }) + } +} +impl + TryFrom<( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); let processing_information = ProcessingInformation::from((item, None)); let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); Ok(Self { processing_information, payment_information, order_information, client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -363,10 +589,8 @@ impl let processing_information = ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); let client_reference_information = ClientReferenceInformation::from(item); - let expiration_month = apple_pay_data.get_expiry_month()?; let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; - let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation { tokenized_card: TokenizedCard { number: apple_pay_data.application_primary_account_number, @@ -376,12 +600,18 @@ impl expiration_month, }, }); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); Ok(Self { processing_information, payment_information, order_information, client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -402,7 +632,6 @@ impl let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); - let payment_information = PaymentInformation::GooglePay(GooglePayPaymentInformation { fluid_data: FluidData { value: Secret::from( @@ -410,16 +639,21 @@ impl ), }, }); - let processing_information = ProcessingInformation::from((item, Some(PaymentSolution::GooglePay))); let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); Ok(Self { processing_information, payment_information, order_information, client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, }) } } @@ -434,14 +668,50 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> match item.router_data.request.payment_method_data.clone() { payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - payments::WalletData::ApplePay(_) => { - let payment_method_token = item.router_data.get_payment_method_token()?; - match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - Self::try_from((item, decrypt_data)) - } - types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + payments::WalletData::ApplePay(apple_pay_data) => { + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + None => { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let processing_information = ProcessingInformation::from(( + item, + Some(PaymentSolution::ApplePay), + )); + let client_reference_information = + ClientReferenceInformation::from(item); + let payment_information = PaymentInformation::ApplePayToken( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + ); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from( + metadata.peek().to_owned(), + ) + }); + Ok(Self { + processing_information, + payment_information, + order_information, + merchant_defined_information, + client_reference_information, + consumer_authentication_information: None, + }) } } } @@ -497,7 +767,65 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthSetupRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> + for BankOfAmericaAuthSetupRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + let client_reference_information = ClientReferenceInformation::from(item); + Ok(Self { + payment_information, + client_reference_information, + }) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ) + .into()) + } + } + } +} + +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BankofamericaPaymentStatus { Authorized, @@ -507,8 +835,16 @@ pub enum BankofamericaPaymentStatus { Reversed, Pending, Declined, + Rejected, + Challenge, AuthorizedPendingReview, + AuthorizedRiskDeclined, Transmitted, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + //PartialAuthorized, not being consumed yet. } impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { @@ -536,13 +872,43 @@ impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { BankofamericaPaymentStatus::Voided | BankofamericaPaymentStatus::Reversed => { Self::Voided } - BankofamericaPaymentStatus::Failed | BankofamericaPaymentStatus::Declined => { - Self::Failure + BankofamericaPaymentStatus::Failed + | BankofamericaPaymentStatus::Declined + | BankofamericaPaymentStatus::AuthorizedRiskDeclined + | BankofamericaPaymentStatus::InvalidRequest + | BankofamericaPaymentStatus::Rejected + | BankofamericaPaymentStatus::ServerError => Self::Failure, + BankofamericaPaymentStatus::PendingAuthentication => Self::AuthenticationPending, + BankofamericaPaymentStatus::PendingReview | BankofamericaPaymentStatus::Challenge => { + Self::Pending } } } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationResponse { + access_token: String, + device_data_collection_url: String, + reference_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientAuthSetupInfoResponse { + id: String, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BankOfAmericaAuthSetupResponse { + ClientAuthSetupInfo(ClientAuthSetupInfoResponse), + ErrorInformation(BankOfAmericaErrorInformationResponse), +} + #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum BankOfAmericaPaymentsResponse { @@ -550,115 +916,164 @@ pub enum BankOfAmericaPaymentsResponse { ErrorInformation(BankOfAmericaErrorInformationResponse), } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaClientReferenceResponse { id: String, status: BankofamericaPaymentStatus, client_reference_information: ClientReferenceInformation, + processor_information: Option, + risk_information: Option, + error_information: Option, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaErrorInformationResponse { id: String, error_information: BankOfAmericaErrorInformation, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct BankOfAmericaErrorInformation { reason: Option, message: Option, } -impl - TryFrom< - types::ResponseRouterData< - F, - BankOfAmericaPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData +impl + From<( + &BankOfAmericaErrorInformationResponse, + types::ResponseRouterData, + Option, + )> for types::RouterData { - type Error = error_stack::Report; - fn try_from( - item: types::ResponseRouterData< - F, - BankOfAmericaPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - ) -> Result { - match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from(( - info_response.status, - item.data.request.is_auto_capture()?, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - info_response.id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id), - ), - incremental_authorization_allowed: None, - }), + fn from( + (error_response, item, transaction_status): ( + &BankOfAmericaErrorInformationResponse, + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + T, + types::PaymentsResponseData, + >, + Option, + ), + ) -> Self { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + match transaction_status { + Some(status) => Self { + response, + status, ..item.data - }), - BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: error_response - .error_information - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: error_response.error_information.reason, - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: None, - }), + }, + None => Self { + response, ..item.data - }), + }, } } } +fn get_error_response_if_failure( + (info_response, status, http_code): ( + &BankOfAmericaClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Option { + if utils::is_payment_failure(status) { + Some(types::ErrorResponse::from(( + &info_response.error_information, + &info_response.risk_information, + http_code, + info_response.id.clone(), + ))) + } else { + None + } +} + +fn get_payment_response( + (info_response, status, http_code): ( + &BankOfAmericaClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Result { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(error), + None => Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: None, + mandate_reference: None, + connector_metadata: info_response + .processor_information + .as_ref() + .map(|processor_information| serde_json::json!({"avs_response": processor_information.avs})), + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed: None, + }), + } +} + impl TryFrom< types::ResponseRouterData< F, - BankOfAmericaPaymentsResponse, - types::PaymentsCaptureData, + BankOfAmericaAuthSetupResponse, + types::PaymentsAuthorizeData, types::PaymentsResponseData, >, - > for types::RouterData + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, - BankOfAmericaPaymentsResponse, - types::PaymentsCaptureData, + BankOfAmericaAuthSetupResponse, + types::PaymentsAuthorizeData, types::PaymentsResponseData, >, ) -> Result { match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from((info_response.status, true)), + BankOfAmericaAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - info_response.id.clone(), - ), - redirection_data: None, + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(services::RedirectForm::CybersourceAuthSetup { + access_token: info_response + .consumer_authentication_information + .access_token, + ddc_url: info_response + .consumer_authentication_information + .device_data_collection_url, + reference_id: info_response + .consumer_authentication_information + .reference_id, + }), mandate_reference: None, connector_metadata: None, network_txn_id: None, @@ -666,26 +1081,294 @@ impl info_response .client_reference_information .code - .unwrap_or(info_response.id), + .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, }), ..item.data }), - BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: error_response - .error_information - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: error_response.error_information.reason, - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: None, - }), - ..item.data - }), + BankOfAmericaAuthSetupResponse::ErrorInformation(error_response) => { + let error_reason = error_response + .error_information + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason; + Ok(Self { + response: Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }), + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationRequest { + return_url: String, + reference_id: String, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthEnrollmentRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationRequest, + order_information: OrderInformationWithBill, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct BankOfAmericaRedirectionAuthResponse { + pub transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationValidateRequest { + authentication_transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaAuthValidateRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationValidateRequest, + order_information: OrderInformation, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum BankOfAmericaPreProcessingRequest { + AuthEnrollment(BankOfAmericaAuthEnrollmentRequest), + AuthValidate(BankOfAmericaAuthValidateRequest), +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>> + for BankOfAmericaPreProcessingRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>, + ) -> Result { + let client_reference_information = ClientReferenceInformation { + code: Some(item.router_data.connector_request_reference_id.clone()), + }; + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "payment_method_data", + }, + )?; + let payment_information = match payment_method_data { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + Ok(PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + })) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + )) + } + }?; + + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + + let amount_details = Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + }, + )?, + }; + + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => { + let reference_id = param + .clone() + .peek() + .split_once('=') + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.params.reference_id", + })? + .1 + .to_string(); + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill { + amount_details, + bill_to, + }; + Ok(Self::AuthEnrollment(BankOfAmericaAuthEnrollmentRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + BankOfAmericaConsumerAuthInformationRequest { + return_url: item.router_data.request.get_complete_authorize_url()?, + reference_id, + }, + order_information, + })) + } + Some(_) | None => { + let redirect_payload: BankOfAmericaRedirectionAuthResponse = redirect_response + .payload + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + })? + .peek() + .clone() + .parse_value("BankOfAmericaRedirectionAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let order_information = OrderInformation { amount_details }; + Ok(Self::AuthValidate(BankOfAmericaAuthValidateRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + BankOfAmericaConsumerAuthInformationValidateRequest { + authentication_transaction_id: redirect_payload.transaction_id, + }, + order_information, + })) + } + } + } +} + +impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_data", + }, + )?; + match payment_method_data { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), + ) + .into()) + } + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum BankOfAmericaAuthEnrollmentStatus { + PendingAuthentication, + AuthenticationSuccessful, + AuthenticationFailed, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthValidateResponse { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + specification_version: Option, + directory_server_transaction_id: Option, + indicator: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BankOfAmericaThreeDSMetadata { + three_ds_data: BankOfAmericaConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaConsumerAuthInformationEnrollmentResponse { + access_token: Option, + step_up_url: Option, + //Added to segregate the three_ds_data in a separate struct + #[serde(flatten)] + validate_response: BankOfAmericaConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientAuthCheckInfoResponse { + id: String, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: BankOfAmericaConsumerAuthInformationEnrollmentResponse, + status: BankOfAmericaAuthEnrollmentStatus, + error_information: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BankOfAmericaPreProcessingResponse { + ClientAuthCheckInfo(Box), + ErrorInformation(BankOfAmericaErrorInformationResponse), +} + +impl From for enums::AttemptStatus { + fn from(item: BankOfAmericaAuthEnrollmentStatus) -> Self { + match item { + BankOfAmericaAuthEnrollmentStatus::PendingAuthentication => Self::AuthenticationPending, + BankOfAmericaAuthEnrollmentStatus::AuthenticationSuccessful => { + Self::AuthenticationSuccessful + } + BankOfAmericaAuthEnrollmentStatus::AuthenticationFailed => Self::AuthenticationFailed, } } } @@ -694,56 +1377,269 @@ impl TryFrom< types::ResponseRouterData< F, - BankOfAmericaPaymentsResponse, - types::PaymentsCancelData, + BankOfAmericaPreProcessingResponse, + types::PaymentsPreProcessingData, types::PaymentsResponseData, >, - > for types::RouterData + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, - BankOfAmericaPaymentsResponse, - types::PaymentsCancelData, + BankOfAmericaPreProcessingResponse, + types::PaymentsPreProcessingData, types::PaymentsResponseData, >, ) -> Result { match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from((info_response.status, false)), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + BankOfAmericaPreProcessingResponse::ClientAuthCheckInfo(info_response) => { + let status = enums::AttemptStatus::from(info_response.status); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + let response = Err(types::ErrorResponse::from(( + &info_response.error_information, + &risk_info, + item.http_code, info_response.id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some( + ))); + + Ok(Self { + status, + response, + ..item.data + }) + } else { + let connector_response_reference_id = Some( info_response .client_reference_information .code - .unwrap_or(info_response.id), - ), - incremental_authorization_allowed: None, - }), - ..item.data - }), - BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: error_response - .error_information - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: error_response.error_information.reason, + .unwrap_or(info_response.id.clone()), + ); + + let redirection_data = match ( + info_response + .consumer_authentication_information + .access_token, + info_response + .consumer_authentication_information + .step_up_url, + ) { + (Some(access_token), Some(step_up_url)) => { + Some(services::RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + }) + } + _ => None, + }; + let three_ds_data = serde_json::to_value( + info_response + .consumer_authentication_information + .validate_response, + ) + .into_report() + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data, + mandate_reference: None, + connector_metadata: Some( + serde_json::json!({"three_ds_data":three_ds_data}), + ), + network_txn_id: None, + connector_response_reference_id, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } + } + BankOfAmericaPreProcessingResponse::ErrorInformation(ref error_response) => { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, - }), - ..item.data - }), + connector_transaction_id: Some(error_response.id.clone()), + }); + Ok(Self { + response, + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), true)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from((&error_response.clone(), item, None))) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BankOfAmericaPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from((&error_response.clone(), item, None))) + } } } } @@ -761,6 +1657,7 @@ pub struct BankOfAmericaApplicationInfoResponse { id: String, application_information: ApplicationInformation, client_reference_information: Option, + error_information: Option, } #[derive(Debug, Deserialize)] @@ -789,25 +1686,44 @@ impl >, ) -> Result { match item.response { - BankOfAmericaTransactionResponse::ApplicationInformation(app_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from(( + BankOfAmericaTransactionResponse::ApplicationInformation(app_response) => { + let status = enums::AttemptStatus::foreign_from(( app_response.application_information.status, item.data.request.is_auto_capture()?, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(app_response.id.clone()), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: app_response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(app_response.id)), - incremental_authorization_allowed: None, - }), - ..item.data - }), + )); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + Ok(Self { + response: Err(types::ErrorResponse::from(( + &app_response.error_information, + &risk_info, + item.http_code, + app_response.id.clone(), + ))), + status: enums::AttemptStatus::Failure, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + app_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: app_response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(app_response.id)), + incremental_authorization_allowed: None, + }), + ..item.data + }) + } + } BankOfAmericaTransactionResponse::ErrorInformation(error_response) => Ok(Self { status: item.data.status, response: Ok(types::PaymentsResponseData::TransactionResponse { @@ -838,6 +1754,8 @@ pub struct OrderInformation { pub struct BankOfAmericaCaptureRequest { order_information: OrderInformation, client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, } impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>> @@ -847,6 +1765,10 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>> fn try_from( value: &BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>, ) -> Result { + let merchant_defined_information = + value.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); Ok(Self { order_information: OrderInformation { amount_details: Amount { @@ -857,6 +1779,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>> client_reference_information: ClientReferenceInformation { code: Some(value.router_data.connector_request_reference_id.clone()), }, + merchant_defined_information, }) } } @@ -866,6 +1789,9 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCaptureRouterData>> pub struct BankOfAmericaVoidRequest { client_reference_information: ClientReferenceInformation, reversal_information: ReversalInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, + // The connector documentation does not mention the merchantDefinedInformation field for Void requests. But this has been still added because it works! } #[derive(Debug, Serialize)] @@ -882,6 +1808,10 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCancelRouterData>> fn try_from( value: &BankOfAmericaRouterData<&types::PaymentsCancelRouterData>, ) -> Result { + let merchant_defined_information = + value.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); Ok(Self { client_reference_information: ClientReferenceInformation { code: Some(value.router_data.connector_request_reference_id.clone()), @@ -904,6 +1834,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCancelRouterData>> field_name: "Cancellation Reason", })?, }, + merchant_defined_information, }) } } @@ -1016,34 +1947,42 @@ impl TryFrom, pub status: Option, pub message: Option, - pub reason: Option, + pub reason: Option, pub details: Option>, } -#[derive(Debug, Deserialize, strum::Display)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BankOfAmericaServerErrorResponse { + pub status: Option, + pub message: Option, + pub reason: Option, +} + +#[derive(Debug, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Reason { - MissingField, - InvalidData, - DuplicateRequest, - InvalidCard, - AuthAlreadyReversed, - CardTypeNotAccepted, - InvalidMerchantConfiguration, - ProcessorUnavailable, - InvalidAmount, - InvalidCardType, - InvalidPaymentId, - NotSupported, SystemError, ServerTimeout, ServiceTimeout, } +#[derive(Debug, Deserialize)] +pub struct BankOfAmericaAuthenticationErrorResponse { + pub response: AuthenticationErrorInformation, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum BankOfAmericaErrorResponse { + AuthenticationError(BankOfAmericaAuthenticationErrorResponse), + StandardError(BankOfAmericaStandardErrorResponse), +} + #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Details { @@ -1056,3 +1995,62 @@ pub struct ErrorInformation { pub message: String, pub reason: String, } + +#[derive(Debug, Default, Deserialize)] +pub struct AuthenticationErrorInformation { + pub rmsg: String, +} + +impl + From<( + &Option, + &Option, + u16, + String, + )> for types::ErrorResponse +{ + fn from( + (error_data, risk_information, status_code, transaction_id): ( + &Option, + &Option, + u16, + String, + ), + ) -> Self { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| format!(" , {}", risk_info.name)) + .collect::>() + .join("") + }) + }) + .unwrap_or(Some("".to_string())); + let error_reason = error_data + .clone() + .map(|error_details| { + error_details.message.unwrap_or("".to_string()) + + &avs_message.unwrap_or("".to_string()) + }) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_data + .clone() + .and_then(|error_details| error_details.reason); + + Self { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason.clone()), + status_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(transaction_id.clone()), + } + } +} diff --git a/crates/router/src/connector/bitpay.rs b/crates/router/src/connector/bitpay.rs index b6bbaafc4a3..4a8108d04b8 100644 --- a/crates/router/src/connector/bitpay.rs +++ b/crates/router/src/connector/bitpay.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::ResultExt; use masking::PeekInterface; use transformers as bitpay; @@ -23,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -146,6 +146,20 @@ impl types::PaymentsResponseData, > for Bitpay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Bitpay".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -175,21 +189,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bitpay::BitpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?; - let bitpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bitpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -207,7 +216,7 @@ impl ConnectorIntegration for Bluesnap { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Bluesnap".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Bluesnap {} @@ -267,14 +282,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bluesnap::BluesnapVoidRequest::try_from(req)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -287,7 +297,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -452,12 +462,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -472,7 +477,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bluesnap::BluesnapCreateWalletToken::try_from(req)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -562,7 +562,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -646,22 +646,12 @@ impl ConnectorIntegration { let connector_req = bluesnap::BluesnapPaymentsTokenRequest::try_from(&connector_router_data)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } _ => { let connector_req = bluesnap::BluesnapPaymentsRequest::try_from(&connector_router_data)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -681,7 +671,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -783,12 +773,7 @@ impl ))?; let connector_req = bluesnap::BluesnapCompletePaymentsRequest::try_from(&connector_router_data)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -805,7 +790,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -873,7 +858,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -881,12 +866,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -901,7 +881,7 @@ impl ConnectorIntegration { - Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - api_models::payments::PaymentIdType::PaymentAttemptId( - webhook_body.merchant_transaction_id, - ), - )) + if webhook_body.merchant_transaction_id.is_empty() { + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::ConnectorTransactionId( + webhook_body.reference_number, + ), + )) + } else { + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + webhook_body.merchant_transaction_id, + ), + )) + } } bluesnap::BluesnapWebhookEvents::Refund => { Ok(api_models::webhooks::ObjectReferenceId::RefundId( diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index 3a980aee819..17cdf3b519b 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -529,7 +529,7 @@ impl TryFrom for Boku { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Boku".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -209,14 +222,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuPaymentsRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + ) -> CustomResult { + let connector_req = boku::BokuPaymentsRequest::try_from(req)?; + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -234,7 +242,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuPsyncRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + ) -> CustomResult { + let connector_req = boku::BokuPsyncRequest::try_from(req)?; + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -323,7 +326,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -403,7 +406,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuRefundRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + ) -> CustomResult { + let connector_req = boku::BokuRefundRequest::try_from(req)?; + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -496,7 +494,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuRsyncRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + ) -> CustomResult { + let connector_req = boku::BokuRsyncRequest::try_from(req)?; + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -578,7 +571,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = braintree::BraintreeSessionRequest::try_from(req)?; - let braintree_session_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_session_request)) + ) -> CustomResult { + let connector_req = braintree::BraintreeSessionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -330,16 +324,10 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = - braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -355,7 +343,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -402,6 +390,20 @@ impl > for Braintree { // Not Implemented (R) + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Braintree".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -448,7 +450,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version.clone(); let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -459,17 +461,12 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeCaptureRequest::try_from( &connector_router_data, )?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::NotImplemented( "get_request_body method".to_string(), @@ -493,7 +490,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreePSyncRequest::try_from(req)?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -632,7 +624,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -788,25 +780,15 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreePaymentsRequest::try_from( &connector_router_data, )?; - let braintree_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => { - let connector_request = braintree::BraintreePaymentsRequest::try_from(req)?; - let braintree_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_payment_request)) + let connector_req = braintree::BraintreePaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -939,7 +921,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeCancelRequest::try_from(req)?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -1080,7 +1057,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -1091,25 +1068,15 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeRefundRequest::try_from( connector_router_data, )?; - let braintree_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => { - let connector_request = braintree::BraintreeRefundRequest::try_from(req)?; - let braintree_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_refund_request)) + let connector_req = braintree::BraintreeRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -1126,7 +1093,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeRSyncRequest::try_from(req)?; - let braintree_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -1247,7 +1209,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( &self.get_currency_unit(), @@ -1622,22 +1584,18 @@ impl let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreePaymentsRequest::try_from( &connector_router_data, )?; - let braintree_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::NotImplemented( "get_request_body method".to_string(), ))?, } } + fn build_request( &self, req: &types::PaymentsCompleteAuthorizeRouterData, @@ -1655,7 +1613,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index f6c1bfc46b0..e0baf034f72 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -297,11 +297,11 @@ fn build_error_response( get_error_response( response - .get(0) + .first() .and_then(|err_details| err_details.extensions.as_ref()) .and_then(|extensions| extensions.legacy_code.clone()), response - .get(0) + .first() .map(|err_details| err_details.message.clone()), reason, http_code, @@ -867,7 +867,9 @@ impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest { expiration_year: card_data.card_exp_year, expiration_month: card_data.card_exp_month, cvv: card_data.card_cvc, - cardholder_name: card_data.card_holder_name, + cardholder_name: card_data + .card_holder_name + .unwrap_or(Secret::new("".to_string())), }, }; Ok(Self { diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index 6749f418934..5a628f775b6 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -1,16 +1,16 @@ pub mod transformers; - use std::fmt::Debug; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{PeekInterface, Secret}; use transformers as cashtocode; +use super::utils as connector_utils; use crate::{ configs::settings::{self}, - connector::{utils as connector_utils, utils as conn_utils}, core::errors::{self, CustomResult}, headers, services::{ @@ -23,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, domain, storage, ErrorResponse, Response, }, - utils::{self, ByteSliceExt, BytesExt}, + utils::{ByteSliceExt, BytesExt}, }; #[derive(Debug, Clone)] @@ -158,6 +158,20 @@ impl types::PaymentsResponseData, > for Cashtocode { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Cashtocode".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -205,14 +219,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = cashtocode::CashtocodePaymentsRequest::try_from(req)?; - let cashtocode_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cashtocode_req)) + ) -> CustomResult { + let connector_req = cashtocode::CashtocodePaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -230,7 +239,7 @@ impl ConnectorIntegration, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = conn_utils::get_header_key_value("authorization", request.headers)?; + let base64_signature = + connector_utils::get_header_key_value("authorization", request.headers)?; let signature = base64_signature.as_bytes().to_owned(); Ok(signature) } diff --git a/crates/router/src/connector/cashtocode/transformers.rs b/crates/router/src/connector/cashtocode/transformers.rs index b38ca4b6713..9aa6286a963 100644 --- a/crates/router/src/connector/cashtocode/transformers.rs +++ b/crates/router/src/connector/cashtocode/transformers.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use common_utils::{ext_traits::ValueExt, pii::Email}; +pub use common_utils::request::Method; +use common_utils::{errors::CustomResult, ext_traits::ValueExt, pii::Email}; use error_stack::{IntoReport, ResultExt}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -185,7 +186,7 @@ pub enum CashtocodePaymentsResponse { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CashtocodePaymentsResponseData { - pub pay_url: String, + pub pay_url: url::Url, } #[derive(Debug, Clone, Deserialize)] @@ -195,17 +196,44 @@ pub struct CashtocodePaymentsSyncResponse { pub amount: i64, } -impl +fn get_redirect_form_data( + payment_method_type: &enums::PaymentMethodType, + response_data: CashtocodePaymentsResponseData, +) -> CustomResult { + match payment_method_type { + enums::PaymentMethodType::ClassicReward => Ok(services::RedirectForm::Form { + //redirect form is manually constructed because the connector for this pm type expects query params in the url + endpoint: response_data.pay_url.to_string(), + method: services::Method::Post, + form_fields: Default::default(), + }), + enums::PaymentMethodType::Evoucher => Ok(services::RedirectForm::from(( + //here the pay url gets parsed, and query params are sent as formfields as the connector expects + response_data.pay_url, + services::Method::Get, + ))), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("CashToCode"), + ))?, + } +} + +impl TryFrom< - types::ResponseRouterData, - > for types::RouterData + types::ResponseRouterData< + F, + CashtocodePaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, CashtocodePaymentsResponse, - T, + types::PaymentsAuthorizeData, types::PaymentsResponseData, >, ) -> Result { @@ -222,11 +250,13 @@ impl }), ), CashtocodePaymentsResponse::CashtoCodeData(response_data) => { - let redirection_data = services::RedirectForm::Form { - endpoint: response_data.pay_url, - method: services::Method::Post, - form_fields: Default::default(), - }; + let payment_method_type = item + .data + .request + .payment_method_type + .as_ref() + .ok_or(errors::ConnectorError::MissingPaymentMethodType)?; + let redirection_data = get_redirect_form_data(payment_method_type, response_data)?; ( enums::AttemptStatus::AuthenticationPending, Ok(types::PaymentsResponseData::TransactionResponse { @@ -272,10 +302,10 @@ impl >, ) -> Result { Ok(Self { - status: enums::AttemptStatus::Charged, + status: enums::AttemptStatus::Charged, // Charged status is hardcoded because cashtocode do not support Psync, and we only receive webhooks when payment is succeeded, this tryFrom is used for CallConnectorAction. response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.data.attempt_id.clone(), + item.data.attempt_id.clone(), //in response they only send PayUrl, so we use attempt_id as connector_transaction_id ), redirection_data: None, mandate_reference: None, diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index 312a91196de..382737a28f6 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -29,7 +29,8 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -210,14 +211,9 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = checkout::TokenRequest::try_from(req)?; - let checkout_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -231,7 +227,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -290,6 +286,20 @@ impl > for Checkout { // Issue: #173 + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Checkout".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -318,7 +328,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -326,12 +336,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -347,7 +352,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -516,12 +518,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -542,7 +539,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = checkout::PaymentVoidRequest::try_from(req)?; - let checkout_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -622,7 +614,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -700,12 +692,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(body)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -720,7 +707,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let checkout_req = transformers::construct_file_upload_request(req.clone())?; - Ok(Some(checkout_req)) + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = transformers::construct_file_upload_request(req.clone())?; + Ok(RequestContent::FormData(connector_req)) } fn build_request( @@ -988,8 +973,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let checkout_req = checkout::Evidence::try_from(req)?; - let checkout_req_string = types::RequestBody::log_and_get_request_body( - &checkout_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req_string)) + ) -> CustomResult { + let connector_req = checkout::Evidence::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1086,7 +1067,7 @@ impl .headers(types::SubmitEvidenceType::get_headers( self, req, connectors, )?) - .body(types::SubmitEvidenceType::get_request_body( + .set_body(types::SubmitEvidenceType::get_request_body( self, req, connectors, )?) .build(); diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 37c038c22af..6e7656c38a6 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -6,7 +6,10 @@ use time::PrimitiveDateTime; use url::Url; use crate::{ - connector::utils::{self, ApplePayDecrypt, PaymentsCaptureRequestData, RouterData, WalletData}, + connector::utils::{ + self, to_connector_meta, ApplePayDecrypt, PaymentsCaptureRequestData, RouterData, + WalletData, + }, consts, core::errors, services, @@ -241,6 +244,17 @@ pub struct PaymentsRequest { pub reference: String, } +#[derive(Debug, Serialize, Deserialize)] +pub struct CheckoutMeta { + pub psync_flow: CheckoutPaymentIntent, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum CheckoutPaymentIntent { + Capture, + Authorize, +} + #[derive(Debug, Serialize)] pub struct CheckoutThreeDS { enabled: bool, @@ -461,7 +475,27 @@ impl ForeignFrom<(CheckoutPaymentStatus, Option)> for enum if capture_method == Some(enums::CaptureMethod::Automatic) || capture_method.is_none() { - Self::Charged + Self::Pending + } else { + Self::Authorized + } + } + CheckoutPaymentStatus::Captured => Self::Charged, + CheckoutPaymentStatus::Declined => Self::Failure, + CheckoutPaymentStatus::Pending => Self::AuthenticationPending, + CheckoutPaymentStatus::CardVerified => Self::Pending, + } + } +} + +impl ForeignFrom<(CheckoutPaymentStatus, CheckoutPaymentIntent)> for enums::AttemptStatus { + fn foreign_from(item: (CheckoutPaymentStatus, CheckoutPaymentIntent)) -> Self { + let (status, psync_flow) = item; + + match status { + CheckoutPaymentStatus::Authorized => { + if psync_flow == CheckoutPaymentIntent::Capture { + Self::Pending } else { Self::Authorized } @@ -533,6 +567,24 @@ pub struct Balances { available_to_capture: i32, } +fn get_connector_meta( + capture_method: enums::CaptureMethod, +) -> CustomResult { + match capture_method { + enums::CaptureMethod::Automatic => Ok(serde_json::json!(CheckoutMeta { + psync_flow: CheckoutPaymentIntent::Capture, + })), + enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple => { + Ok(serde_json::json!(CheckoutMeta { + psync_flow: CheckoutPaymentIntent::Authorize, + })) + } + enums::CaptureMethod::Scheduled => { + Err(errors::ConnectorError::CaptureMethodNotSupported.into()) + } + } +} + impl TryFrom> for types::PaymentsAuthorizeRouterData { @@ -540,6 +592,9 @@ impl TryFrom> fn try_from( item: types::PaymentsResponseRouterData, ) -> Result { + let connector_meta = + get_connector_meta(item.data.request.capture_method.unwrap_or_default())?; + let redirection_data = item.response.links.redirect.map(|href| { services::RedirectForm::from((href.redirection_url, services::Method::Get)) }); @@ -570,7 +625,7 @@ impl TryFrom> resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data, mandate_reference: None, - connector_metadata: None, + connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: Some( item.response.reference.unwrap_or(item.response.id), @@ -595,8 +650,10 @@ impl TryFrom> let redirection_data = item.response.links.redirect.map(|href| { services::RedirectForm::from((href.redirection_url, services::Method::Get)) }); + let checkout_meta: CheckoutMeta = + to_connector_meta(item.data.request.connector_meta.clone())?; let status = - enums::AttemptStatus::foreign_from((item.response.status, item.response.balances)); + enums::AttemptStatus::foreign_from((item.response.status, checkout_meta.psync_flow)); let error_response = if status == enums::AttemptStatus::Failure { Some(types::ErrorResponse { status_code: item.http_code, @@ -772,6 +829,9 @@ impl TryFrom> fn try_from( item: types::PaymentsCaptureResponseRouterData, ) -> Result { + let connector_meta = serde_json::json!(CheckoutMeta { + psync_flow: CheckoutPaymentIntent::Capture, + }); let (status, amount_captured) = if item.http_code == 202 { ( enums::AttemptStatus::Charged, @@ -794,7 +854,7 @@ impl TryFrom> resource_id: types::ResponseId::ConnectorTransactionId(resource_id), redirection_data: None, mandate_reference: None, - connector_metadata: None, + connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: item.response.reference, incremental_authorization_allowed: None, @@ -966,10 +1026,7 @@ impl utils::MultipleCaptureSyncResponse for Box { self.status == CheckoutPaymentStatus::Captured } fn get_amount_captured(&self) -> Option { - match self.amount { - Some(amount) => amount.try_into().ok(), - None => None, - } + self.amount.map(Into::into) } } diff --git a/crates/router/src/connector/coinbase.rs b/crates/router/src/connector/coinbase.rs index b294a4474f6..76f88b66326 100644 --- a/crates/router/src/connector/coinbase.rs +++ b/crates/router/src/connector/coinbase.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as coinbase; @@ -24,7 +24,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{BytesExt, Encode}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -157,6 +157,20 @@ impl types::PaymentsResponseData, > for Coinbase { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Coinbase".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -186,14 +200,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = coinbase::CoinbasePaymentsRequest::try_from(req)?; - let coinbase_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(coinbase_payment_request)) + ) -> CustomResult { + let connector_req = coinbase::CoinbasePaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -210,7 +219,7 @@ impl ConnectorIntegration, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let api_method; - let payload = match self.get_request_body(req, connectors)? { - Some(val) => { - let body = types::RequestBody::get_inner_value(val).peek().to_owned(); - api_method = "POST".to_string(); - let md5_payload = crypto::Md5 - .generate_digest(body.as_bytes()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - encode(md5_payload) - } - None => { - api_method = "GET".to_string(); - String::default() - } - }; + let api_method = self.get_http_method().to_string(); + let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?) + .peek() + .to_owned(); + let md5_payload = crypto::Md5 + .generate_digest(body.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let payload = encode(md5_payload); let now = date_time::date_as_yyyymmddthhmmssmmmz() .into_report() @@ -190,6 +184,20 @@ impl types::PaymentsResponseData, > for Cryptopay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Cryptopay".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -219,21 +227,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cryptopay::CryptopayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = - cryptopay::CryptopayPaymentsRequest::try_from(&connector_router_data)?; - let cryptopay_req = types::RequestBody::log_and_get_request_body( - &connector_request, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cryptopay_req)) + let connector_req = cryptopay::CryptopayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -251,7 +253,7 @@ impl ConnectorIntegration services::Method { + services::Method::Get + } + fn get_url( &self, req: &types::PaymentsSyncRouterData, diff --git a/crates/router/src/connector/cryptopay/transformers.rs b/crates/router/src/connector/cryptopay/transformers.rs index 3af604c786b..4102945b201 100644 --- a/crates/router/src/connector/cryptopay/transformers.rs +++ b/crates/router/src/connector/cryptopay/transformers.rs @@ -82,10 +82,9 @@ impl TryFrom<&CryptopayRouterData<&types::PaymentsAuthorizeRouterData>> | api_models::payments::PaymentMethodData::Voucher(_) | api_models::payments::PaymentMethodData::GiftCard(_) | api_models::payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotSupported { - message: utils::SELECTED_PAYMENT_METHOD.to_string(), - connector: "CryptoPay", - }) + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("CryptoPay"), + )) } }?; Ok(cryptopay_request) diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 631b2f8c97e..ac2d16c9610 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -3,6 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -11,6 +12,7 @@ use time::OffsetDateTime; use transformers as cybersource; use url::Url; +use super::utils::{PaymentsAuthorizeRequestData, RouterData}; use crate::{ configs::settings, connector::{utils as connector_utils, utils::RefundsRequestData}, @@ -26,7 +28,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -53,10 +55,20 @@ impl Cybersource { api_secret, } = auth; let is_post_method = matches!(http_method, services::Method::Post); - let digest_str = if is_post_method { "digest " } else { "" }; + let is_patch_method = matches!(http_method, services::Method::Patch); + let is_delete_method = matches!(http_method, services::Method::Delete); + let digest_str = if is_post_method || is_patch_method { + "digest " + } else { + "" + }; let headers = format!("host date (request-target) {digest_str}v-c-merchant-id"); let request_target = if is_post_method { format!("(request-target): post {resource}\ndigest: SHA-256={payload}\n") + } else if is_patch_method { + format!("(request-target): patch {resource}\ndigest: SHA-256={payload}\n") + } else if is_delete_method { + format!("(request-target): delete {resource}\n") } else { format!("(request-target): get {resource}\n") }; @@ -101,44 +113,82 @@ impl ConnectorCommon for Cybersource { &self, res: types::Response, ) -> CustomResult { - let response: cybersource::ErrorResponse = res + let response: cybersource::CybersourceErrorResponse = res .response .parse_struct("Cybersource ErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let details = response.details.unwrap_or_default(); - let connector_reason = details - .iter() - .map(|det| format!("{} : {}", det.field, det.reason)) - .collect::>() - .join(", "); let error_message = if res.status_code == 401 { consts::CONNECTOR_UNAUTHORIZED_ERROR } else { consts::NO_ERROR_MESSAGE }; - - let (code, message) = match response.error_information { - Some(ref error_info) => (error_info.reason.clone(), error_info.message.clone()), - None => ( - response - .reason - .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { - reason.to_string() - }), - response - .message - .map_or(error_message.to_string(), |message| message), - ), - }; - Ok(types::ErrorResponse { - status_code: res.status_code, - code, - message, - reason: Some(connector_reason), - attempt_status: None, - connector_transaction_id: None, - }) + match response { + transformers::CybersourceErrorResponse::StandardError(response) => { + let (code, connector_reason) = match response.error_information { + Some(ref error_info) => (error_info.reason.clone(), error_info.message.clone()), + None => ( + response + .reason + .map_or(consts::NO_ERROR_CODE.to_string(), |reason| { + reason.to_string() + }), + response + .message + .map_or(error_message.to_string(), |message| message), + ), + }; + let message = match response.details { + Some(details) => details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", "), + None => connector_reason.clone(), + }; + + Ok(types::ErrorResponse { + status_code: res.status_code, + code, + message, + reason: Some(connector_reason), + attempt_status: None, + connector_transaction_id: None, + }) + } + transformers::CybersourceErrorResponse::AuthenticationError(response) => { + Ok(types::ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: response.response.rmsg.clone(), + reason: Some(response.response.rmsg), + attempt_status: None, + connector_transaction_id: None, + }) + } + transformers::CybersourceErrorResponse::NotAvailableError(response) => { + let error_response = response + .errors + .iter() + .map(|error_info| { + format!( + "{}: {}", + error_info.error_type.clone().unwrap_or("".to_string()), + error_info.message.clone().unwrap_or("".to_string()) + ) + }) + .collect::>() + .join(" & "); + Ok(types::ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: error_response.clone(), + reason: Some(error_response), + attempt_status: None, + connector_transaction_id: None, + }) + } + } } } @@ -184,10 +234,8 @@ where .skip(base_url.len() - 1) .collect(); let sha256 = self.generate_digest( - cybersource_req - .map_or("{}".to_string(), |s| { - types::RequestBody::get_inner_value(s).expose() - }) + types::RequestBody::get_inner_value(cybersource_req) + .expose() .as_bytes(), ); let http_method = self.get_http_method(); @@ -239,6 +287,9 @@ impl api::PaymentIncrementalAuthorization for Cybersource {} impl api::MandateSetup for Cybersource {} impl api::ConnectorAccessToken for Cybersource {} impl api::PaymentToken for Cybersource {} +impl api::PaymentsPreProcessing for Cybersource {} +impl api::PaymentsCompleteAuthorize for Cybersource {} +impl api::ConnectorMandateRevoke for Cybersource {} impl ConnectorIntegration< @@ -278,14 +329,9 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = cybersource::CybersourceZeroMandateRequest::try_from(req)?; - let cybersource_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_req)) + ) -> CustomResult { + let connector_req = cybersource::CybersourceZeroMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -299,7 +345,7 @@ impl .url(&types::SetupMandateType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -330,6 +376,92 @@ impl } } +impl + ConnectorIntegration< + api::MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + > for Cybersource +{ + fn get_headers( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_http_method(&self) -> services::Method { + services::Method::Delete + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}tms/v1/paymentinstruments/{}", + self.base_url(connectors), + connector_utils::RevokeMandateRequestData::get_connector_mandate_id(&req.request)? + )) + } + fn build_request( + &self, + req: &types::MandateRevokeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Delete) + .url(&types::MandateRevokeType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::MandateRevokeType::get_headers( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::MandateRevokeRouterData, + res: types::Response, + ) -> CustomResult { + if matches!(res.status_code, 204) { + Ok(types::MandateRevokeRouterData { + response: Ok(types::MandateRevokeResponseData { + mandate_status: common_enums::MandateStatus::Revoked, + }), + ..data.clone() + }) + } else { + // If http_code != 204 || http_code != 4xx, we dont know any other response scenario yet. + let response_value: serde_json::Value = serde_json::from_slice(&res.response) + .into_report() + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + let response_string = response_value.to_string(); + + Ok(types::MandateRevokeRouterData { + response: Err(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message: response_string.clone(), + reason: Some(response_string), + status_code: res.status_code, + attempt_status: None, + connector_transaction_id: None, + }), + ..data.clone() + }) + } + } + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} impl ConnectorIntegration for Cybersource { @@ -343,6 +475,113 @@ impl ConnectorIntegration for Cybersource +{ + fn get_headers( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let redirect_response = req.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => Ok(format!( + "{}risk/v1/authentications", + self.base_url(connectors) + )), + Some(_) | None => Ok(format!( + "{}risk/v1/authentication-results", + self.base_url(connectors) + )), + } + } + fn get_request_body( + &self, + req: &types::PaymentsPreProcessingRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = cybersource::CybersourceRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?, + req, + ))?; + let connector_req = + cybersource::CybersourcePreProcessingRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsPreProcessingRouterData, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourcePreProcessingResponse = res + .response + .parse_struct("Cybersource AuthEnrollmentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + impl ConnectorIntegration for Cybersource { @@ -375,21 +614,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = + let connector_req = cybersource::CybersourcePaymentsCaptureRequest::try_from(&connector_router_data)?; - let cybersource_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -404,7 +638,7 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourceServerErrorResponse = res + .response + .parse_struct("CybersourceServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(types::ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } } impl ConnectorIntegration @@ -477,16 +728,6 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - Ok(Some( - types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - )) - } fn build_request( &self, req: &types::PaymentsSyncRouterData, @@ -510,17 +751,11 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}pts/v2/payments/", - api::ConnectorCommon::base_url(self, connectors) - )) + if req.is_three_ds() + && req.request.is_card() + && req.request.connector_mandate_id().is_none() + { + Ok(format!( + "{}risk/v1/authentication-setups", + api::ConnectorCommon::base_url(self, connectors) + )) + } else { + Ok(format!( + "{}pts/v2/payments/", + api::ConnectorCommon::base_url(self, connectors) + )) + } } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = - cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?; - let cybersource_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_payments_request)) + if req.is_three_ds() + && req.request.is_card() + && req.request.connector_mandate_id().is_none() + { + let connector_req = + cybersource::CybersourceAuthSetupRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } else { + let connector_req = + cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } } fn build_request( @@ -591,7 +840,7 @@ impl ConnectorIntegration CustomResult { - let response: cybersource::CybersourcePaymentsResponse = res - .response - .parse_struct("Cybersource PaymentResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let is_auto_capture = - data.request.capture_method == Some(diesel_models::enums::CaptureMethod::Automatic); - types::RouterData::try_from(( - types::ResponseRouterData { + if data.is_three_ds() + && data.request.is_card() + && data.request.connector_mandate_id().is_none() + { + let response: cybersource::CybersourceAuthSetupResponse = res + .response + .parse_struct("Cybersource AuthSetupResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } else { + let response: cybersource::CybersourcePaymentsResponse = res + .response + .parse_struct("Cybersource PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, + }) + } + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } + + fn get_5xx_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourceServerErrorResponse = res + .response + .parse_struct("CybersourceServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, }, - is_auto_capture, + None => None, + }; + Ok(types::ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } +} + +impl + ConnectorIntegration< + api::CompleteAuthorize, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + > for Cybersource +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}pts/v2/payments/", + api::ConnectorCommon::base_url(self, connectors) )) - .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = cybersource::CybersourceRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = + cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourcePaymentsResponse = res + .response + .parse_struct("Cybersource PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) } fn get_error_response( @@ -625,6 +998,33 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourceServerErrorResponse = res + .response + .parse_struct("CybersourceServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(types::ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + }) + } } impl ConnectorIntegration @@ -645,9 +1045,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( - "{}pts/v2/payments/{}/voids", - self.base_url(connectors), - connector_payment_id + "{}pts/v2/payments/{connector_payment_id}/reversals", + self.base_url(connectors) )) } @@ -657,13 +1056,26 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - Ok(Some( - types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - )) + ) -> CustomResult { + let connector_router_data = cybersource::CybersourceRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Amount", + })?, + req, + ))?; + let connector_req = cybersource::CybersourceVoidRequest::try_from(&connector_router_data)?; + + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -677,7 +1089,7 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + fn get_5xx_error_response( + &self, + res: types::Response, + ) -> CustomResult { + let response: cybersource::CybersourceServerErrorResponse = res + .response + .parse_struct("CybersourceServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(types::ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } } impl api::Refund for Cybersource {} @@ -747,21 +1176,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = + let connector_req = cybersource::CybersourceRefundRequest::try_from(&connector_router_data)?; - let cybersource_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -776,7 +1200,7 @@ impl ConnectorIntegration CustomResult { - let response: cybersource::CybersourcePaymentsResponse = res + let response: cybersource::CybersourceRefundResponse = res .response - .parse_struct("Cybersource PaymentResponse") + .parse_struct("Cybersource RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, @@ -845,9 +1268,6 @@ impl ConnectorIntegration CustomResult { - let response: cybersource::CybersourceTransactionResponse = res + let response: cybersource::CybersourceRsyncResponse = res .response - .parse_struct("Cybersource RefundsSyncResponse") + .parse_struct("Cybersource RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - types::ResponseRouterData { + types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, - } - .try_into() - .change_context(errors::ConnectorError::ResponseHandlingFailed) + }) } fn get_error_response( &self, @@ -891,6 +1309,10 @@ impl self.build_headers(req, connectors) } + fn get_http_method(&self) -> services::Method { + services::Method::Patch + } + fn get_content_type(&self) -> &'static str { self.common_get_content_type() } @@ -912,7 +1334,7 @@ impl &self, req: &types::PaymentsIncrementalAuthorizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -923,13 +1345,7 @@ impl cybersource::CybersourcePaymentsIncrementalAuthorizationRequest::try_from( &connector_router_data, )?; - let cybersource_payments_incremental_authorization_request = - types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_payments_incremental_authorization_request)) + Ok(RequestContent::Json(Box::new(connector_request))) } fn build_request( &self, @@ -946,7 +1362,7 @@ impl .headers(types::IncrementalAuthorizationType::get_headers( self, req, connectors, )?) - .body(types::IncrementalAuthorizationType::get_request_body( + .set_body(types::IncrementalAuthorizationType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index d3f542d2013..8beb81d9236 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -1,19 +1,26 @@ use api_models::payments; -use common_utils::pii; -use masking::Secret; +use base64::Engine; +use common_utils::{ext_traits::ValueExt, pii}; +use error_stack::{IntoReport, ResultExt}; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use crate::{ connector::utils::{ - self, AddressDetailsData, PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, - PhoneDetailsData, RouterData, + self, AddressDetailsData, ApplePayDecrypt, CardData, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, PaymentsPreProcessingData, + PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RouterData, }, consts, core::errors, + services, types::{ self, api::{self, enums as api_enums}, storage::enums, + transformers::ForeignFrom, + ApplePayPredecryptData, }, }; @@ -60,21 +67,19 @@ pub struct CybersourceZeroMandateRequest { impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { - let phone = item.get_billing_phone()?; - let number_with_code = phone.get_number_with_country_code()?; let email = item.request.get_email()?; - let bill_to = build_bill_to(item.get_billing()?, email, number_with_code)?; + let bill_to = build_bill_to(item.get_billing()?, email)?; let order_information = OrderInformationWithBill { amount_details: Amount { total_amount: "0".to_string(), - currency: item.request.currency.to_string(), + currency: item.request.currency, }, bill_to: Some(bill_to), }; let (action_list, action_token_types, authorization_options) = ( Some(vec![CybersourceActionsList::TokenCreate]), - Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), + Some(vec![CybersourceActionsTokenType::PaymentInstrument]), Some(CybersourceAuthorizationOptions { initiator: CybersourcePaymentInitiator { initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), @@ -85,36 +90,122 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { }), ); - let processing_information = ProcessingInformation { - capture: Some(false), - capture_options: None, - action_list, - action_token_types, - authorization_options, - commerce_indicator: CybersourceCommerceIndicator::Internet, - }; - let client_reference_information = ClientReferenceInformation { code: Some(item.connector_request_reference_id.clone()), }; - let payment_information = match item.request.payment_method_data.clone() { + let (payment_information, solution) = match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(ccard) => { - let card = CardDetails::PaymentCard(Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - }); - PaymentInformation { - card, - instrument_identifier: None, - } + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + ( + PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }), + None, + ) } + + api::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(apple_pay_data) => { + match item.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + let expiration_month = decrypt_data.get_expiry_month()?; + let expiration_year = decrypt_data.get_four_digit_expiry_year()?; + ( + PaymentInformation::ApplePay(ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: decrypt_data.application_primary_account_number, + cryptogram: decrypt_data + .payment_data + .online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, + }, + }), + Some(PaymentSolution::ApplePay), + ) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + None => ( + PaymentInformation::ApplePayToken(ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }), + Some(PaymentSolution::ApplePay), + ), + } + } + payments::WalletData::GooglePay(google_pay_data) => ( + PaymentInformation::GooglePay(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE + .encode(google_pay_data.tokenization_data.token), + ), + }, + }), + Some(PaymentSolution::GooglePay), + ), + payments::WalletData::AliPayQr(_) + | payments::WalletData::AliPayRedirect(_) + | payments::WalletData::AliPayHkRedirect(_) + | payments::WalletData::MomoRedirect(_) + | payments::WalletData::KakaoPayRedirect(_) + | payments::WalletData::GoPayRedirect(_) + | payments::WalletData::GcashRedirect(_) + | payments::WalletData::ApplePayRedirect(_) + | payments::WalletData::ApplePayThirdPartySdk(_) + | payments::WalletData::DanaRedirect {} + | payments::WalletData::GooglePayRedirect(_) + | payments::WalletData::GooglePayThirdPartySdk(_) + | payments::WalletData::MbWayRedirect(_) + | payments::WalletData::MobilePayRedirect(_) + | payments::WalletData::PaypalRedirect(_) + | payments::WalletData::PaypalSdk(_) + | payments::WalletData::SamsungPay(_) + | payments::WalletData::TwintRedirect {} + | payments::WalletData::VippsRedirect {} + | payments::WalletData::TouchNGoRedirect(_) + | payments::WalletData::WeChatPayRedirect(_) + | payments::WalletData::WeChatPayQr(_) + | payments::WalletData::CashappQr(_) + | payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ))?, + }, _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), ))?, }; + + let processing_information = ProcessingInformation { + capture: Some(false), + capture_options: None, + action_list, + action_token_types, + authorization_options, + commerce_indicator: String::from("internet"), + payment_solution: solution.map(String::from), + }; Ok(Self { processing_information, payment_information, @@ -131,6 +222,10 @@ pub struct CybersourcePaymentsRequest { payment_information: PaymentInformation, order_information: OrderInformationWithBill, client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + consumer_authentication_information: Option, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, } #[derive(Debug, Serialize)] @@ -139,9 +234,27 @@ pub struct ProcessingInformation { action_list: Option>, action_token_types: Option>, authorization_options: Option, - commerce_indicator: CybersourceCommerceIndicator, + commerce_indicator: String, capture: Option, capture_options: Option, + payment_solution: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthInformation { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + directory_server_transaction_id: Option, + specification_version: Option, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantDefinedInformation { + key: u8, + value: String, } #[derive(Debug, Serialize)] @@ -153,7 +266,7 @@ pub enum CybersourceActionsList { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub enum CybersourceActionsTokenType { - InstrumentIdentifier, + PaymentInstrument, } #[derive(Debug, Serialize)] @@ -186,52 +299,88 @@ pub enum CybersourcePaymentInitiatorTypes { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub enum CybersourceCommerceIndicator { - Internet, +pub struct CaptureOptions { + capture_sequence_number: u32, + total_capture_count: u32, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CaptureOptions { - capture_sequence_number: u32, - total_capture_count: u32, +pub struct CardPaymentInformation { + card: Card, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct PaymentInformation { - card: CardDetails, - instrument_identifier: Option, +pub struct TokenizedCard { + number: Secret, + expiration_month: Secret, + expiration_year: Secret, + cryptogram: Secret, + transaction_type: TransactionType, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CybersoucreInstrumentIdentifier { - id: String, +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenizedCard { + transaction_type: TransactionType, } #[derive(Debug, Serialize)] -#[serde(untagged)] -pub enum CardDetails { - PaymentCard(Card), - MandateCard(MandateCardDetails), +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenPaymentInformation { + fluid_data: FluidData, + tokenized_card: ApplePayTokenizedCard, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Card { - number: cards::CardNumber, - expiration_month: Secret, - expiration_year: Secret, - security_code: Secret, +pub struct ApplePayPaymentInformation { + tokenized_card: TokenizedCard, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MandatePaymentInformation { + payment_instrument: CybersoucrePaymentInstrument, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FluidData { + value: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentInformation { + fluid_data: FluidData, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum PaymentInformation { + Cards(CardPaymentInformation), + GooglePay(GooglePayPaymentInformation), + ApplePay(ApplePayPaymentInformation), + ApplePayToken(ApplePayTokenPaymentInformation), + MandatePayment(MandatePaymentInformation), } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CybersoucrePaymentInstrument { + id: String, +} #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct MandateCardDetails { +pub struct Card { + number: cards::CardNumber, expiration_month: Secret, expiration_year: Secret, + security_code: Secret, + #[serde(rename = "type")] + card_type: Option, } - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OrderInformationWithBill { @@ -255,7 +404,7 @@ pub struct OrderInformation { #[serde(rename_all = "camelCase")] pub struct Amount { total_amount: String, - currency: String, + currency: api_models::enums::Currency, } #[derive(Debug, Serialize)] @@ -265,6 +414,28 @@ pub struct AdditionalAmount { currency: String, } +#[derive(Debug, Serialize)] +pub enum PaymentSolution { + ApplePay, + GooglePay, +} + +#[derive(Debug, Serialize)] +pub enum TransactionType { + #[serde(rename = "1")] + ApplePay, +} + +impl From for String { + fn from(solution: PaymentSolution) -> Self { + let payment_solution = match solution { + PaymentSolution::ApplePay => "001", + PaymentSolution::GooglePay => "012", + }; + payment_solution.to_string() + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BillTo { @@ -276,56 +447,45 @@ pub struct BillTo { postal_code: Secret, country: api_enums::CountryAlpha2, email: pii::Email, - phone_number: Secret, } -// for cybersource each item in Billing is mandatory -fn build_bill_to( - address_details: &payments::Address, - email: pii::Email, - phone_number: Secret, -) -> Result> { - let address = address_details - .address - .as_ref() - .ok_or_else(utils::missing_field_err("billing.address"))?; - Ok(BillTo { - first_name: address.get_first_name()?.to_owned(), - last_name: address.get_last_name()?.to_owned(), - address1: address.get_line1()?.to_owned(), - locality: address.get_city()?.to_owned(), - administrative_area: address.to_state_code()?, - postal_code: address.get_zip()?.to_owned(), - country: address.get_country()?.to_owned(), - email, - phone_number, - }) +impl From<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> + for ClientReferenceInformation +{ + fn from(item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } } -impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> - for CybersourcePaymentsRequest +impl From<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for ClientReferenceInformation { - type Error = error_stack::Report; - fn try_from( - item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, - ) -> Result { - let phone = item.router_data.get_billing_phone()?; - let number_with_code = phone.get_number_with_country_code()?; - let email = item.router_data.request.get_email()?; - let bill_to = build_bill_to(item.router_data.get_billing()?, email, number_with_code)?; + fn from(item: &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } +} - let order_information = OrderInformationWithBill { - amount_details: Amount { - total_amount: item.amount.to_owned(), - currency: item.router_data.request.currency.to_string(), - }, - bill_to: Some(bill_to), - }; +impl + From<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + )> for ProcessingInformation +{ + fn from( + (item, solution): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Option, + ), + ) -> Self { let (action_list, action_token_types, authorization_options) = - if item.router_data.request.setup_future_usage.is_some() { + if item.router_data.request.setup_mandate_details.is_some() { ( Some(vec![CybersourceActionsList::TokenCreate]), - Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), + Some(vec![CybersourceActionsTokenType::PaymentInstrument]), Some(CybersourceAuthorizationOptions { initiator: CybersourcePaymentInitiator { initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), @@ -338,123 +498,658 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> } else { (None, None, None) }; - - let processing_information = ProcessingInformation { + Self { capture: Some(matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) | None )), - capture_options: None, + payment_solution: solution.map(String::from), action_list, action_token_types, authorization_options, - commerce_indicator: CybersourceCommerceIndicator::Internet, - }; - - let client_reference_information = ClientReferenceInformation { - code: Some(item.router_data.connector_request_reference_id.clone()), - }; - let payment_information = match item.router_data.request.payment_method_data.clone() { - api::PaymentMethodData::Card(ccard) => { - let instrument_identifier = - item.router_data - .request - .connector_mandate_id() - .map(|mandate_token_id| CybersoucreInstrumentIdentifier { - id: mandate_token_id, - }); - let card = if instrument_identifier.is_some() { - CardDetails::MandateCard(MandateCardDetails { - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - }) - } else { - CardDetails::PaymentCard(Card { - number: ccard.card_number, - expiration_month: ccard.card_exp_month, - expiration_year: ccard.card_exp_year, - security_code: ccard.card_cvc, - }) - }; - PaymentInformation { - card, - instrument_identifier, - } - } - payments::PaymentMethodData::CardRedirect(_) - | payments::PaymentMethodData::Wallet(_) - | payments::PaymentMethodData::PayLater(_) - | payments::PaymentMethodData::BankRedirect(_) - | payments::PaymentMethodData::BankDebit(_) - | payments::PaymentMethodData::BankTransfer(_) - | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::MandatePayment - | payments::PaymentMethodData::Reward - | payments::PaymentMethodData::Upi(_) - | payments::PaymentMethodData::Voucher(_) - | payments::PaymentMethodData::GiftCard(_) - | payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Cybersource"), - ))? - } - }; - Ok(Self { - processing_information, - payment_information, - order_information, - client_reference_information, - }) + capture_options: None, + commerce_indicator: String::from("internet"), + } } } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CybersourcePaymentsCaptureRequest { - processing_information: ProcessingInformation, - order_information: OrderInformationWithBill, +impl + From<( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &CybersourceConsumerAuthValidateResponse, + )> for ProcessingInformation +{ + fn from( + (item, solution, three_ds_data): ( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + Option, + &CybersourceConsumerAuthValidateResponse, + ), + ) -> Self { + let (action_list, action_token_types, authorization_options) = + if item.router_data.request.setup_future_usage.is_some() { + ( + Some(vec![CybersourceActionsList::TokenCreate]), + Some(vec![CybersourceActionsTokenType::PaymentInstrument]), + Some(CybersourceAuthorizationOptions { + initiator: CybersourcePaymentInitiator { + initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, + }, + merchant_intitiated_transaction: None, + }), + ) + } else { + (None, None, None) + }; + Self { + capture: Some(matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + )), + payment_solution: solution.map(String::from), + action_list, + action_token_types, + authorization_options, + capture_options: None, + commerce_indicator: three_ds_data + .indicator + .to_owned() + .unwrap_or(String::from("internet")), + } + } } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CybersourcePaymentsIncrementalAuthorizationRequest { - processing_information: ProcessingInformation, - order_information: OrderInformationIncrementalAuthorization, +impl + From<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + BillTo, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + BillTo, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to: Some(bill_to), + } + } } -impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> - for CybersourcePaymentsCaptureRequest +impl + From<( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + )> for OrderInformationWithBill { - type Error = error_stack::Report; - fn try_from( - item: &CybersourceRouterData<&types::PaymentsCaptureRouterData>, - ) -> Result { - Ok(Self { - processing_information: ProcessingInformation { - capture_options: Some(CaptureOptions { - capture_sequence_number: 1, - total_capture_count: 1, - }), - action_list: None, - action_token_types: None, - authorization_options: None, - capture: None, - commerce_indicator: CybersourceCommerceIndicator::Internet, - }, - order_information: OrderInformationWithBill { - amount_details: Amount { - total_amount: item.amount.clone(), - currency: item.router_data.request.currency.to_string(), - }, - bill_to: None, + fn from( + (item, bill_to): ( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + BillTo, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, }, - }) + bill_to: Some(bill_to), + } } } -impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> - for CybersourcePaymentsIncrementalAuthorizationRequest -{ +// for cybersource each item in Billing is mandatory +fn build_bill_to( + address_details: &payments::Address, + email: pii::Email, +) -> Result> { + let address = address_details + .address + .as_ref() + .ok_or_else(utils::missing_field_err("billing.address"))?; + let mut state = address.to_state_code()?.peek().clone(); + state.truncate(20); + Ok(BillTo { + first_name: address.get_first_name()?.to_owned(), + last_name: address.get_last_name()?.to_owned(), + address1: address.get_line1()?.to_owned(), + locality: address.get_city()?.to_owned(), + administrative_area: Secret::from(state), + postal_code: address.get_zip()?.to_owned(), + country: address.get_country()?.to_owned(), + email, + }) +} + +impl ForeignFrom for Vec { + fn foreign_from(metadata: Value) -> Self { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()) + .unwrap_or(std::collections::BTreeMap::new()); + let mut vector: Self = Self::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; + } + vector + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::Card, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + + let processing_information = ProcessingInformation::from((item, None)); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + payments::Card, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + payments::Card, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + let client_reference_information = ClientReferenceInformation::from(item); + + let three_ds_info: CybersourceThreeDSMetadata = item + .router_data + .request + .connector_meta + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "connector_meta", + })? + .parse_value("CybersourceThreeDSMetadata") + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "Merchant connector account metadata", + })?; + + let processing_information = + ProcessingInformation::from((item, None, &three_ds_info.three_ds_data)); + + let consumer_authentication_information = Some(CybersourceConsumerAuthInformation { + ucaf_collection_indicator: three_ds_info.three_ds_data.ucaf_collection_indicator, + cavv: three_ds_info.three_ds_data.cavv, + ucaf_authentication_data: three_ds_info.three_ds_data.ucaf_authentication_data, + xid: three_ds_info.three_ds_data.xid, + directory_server_transaction_id: three_ds_info + .three_ds_data + .directory_server_transaction_id, + specification_version: three_ds_info.three_ds_data.specification_version, + }); + + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information, + merchant_defined_information, + }) + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, apple_pay_data): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let processing_information = + ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); + let client_reference_information = ClientReferenceInformation::from(item); + let expiration_month = apple_pay_data.get_expiry_month()?; + let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; + let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: apple_pay_data.application_primary_account_number, + cryptogram: apple_pay_data.payment_data.online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, + }, + }); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::GooglePayWalletData, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, google_pay_data): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + payments::GooglePayWalletData, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + + let payment_information = PaymentInformation::GooglePay(GooglePayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token), + ), + }, + }); + let processing_information = + ProcessingInformation::from((item, Some(PaymentSolution::GooglePay))); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> + for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.connector_mandate_id() { + Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)), + None => { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(apple_pay_data) => { + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + None => { + let email = item.router_data.request.get_email()?; + let bill_to = + build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = + OrderInformationWithBill::from((item, bill_to)); + let processing_information = ProcessingInformation::from(( + item, + Some(PaymentSolution::ApplePay), + )); + let client_reference_information = + ClientReferenceInformation::from(item); + let payment_information = PaymentInformation::ApplePayToken( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + ); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from( + metadata.peek().to_owned(), + ) + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } + } + } + payments::WalletData::GooglePay(google_pay_data) => { + Self::try_from((item, google_pay_data)) + } + payments::WalletData::AliPayQr(_) + | payments::WalletData::AliPayRedirect(_) + | payments::WalletData::AliPayHkRedirect(_) + | payments::WalletData::MomoRedirect(_) + | payments::WalletData::KakaoPayRedirect(_) + | payments::WalletData::GoPayRedirect(_) + | payments::WalletData::GcashRedirect(_) + | payments::WalletData::ApplePayRedirect(_) + | payments::WalletData::ApplePayThirdPartySdk(_) + | payments::WalletData::DanaRedirect {} + | payments::WalletData::GooglePayRedirect(_) + | payments::WalletData::GooglePayThirdPartySdk(_) + | payments::WalletData::MbWayRedirect(_) + | payments::WalletData::MobilePayRedirect(_) + | payments::WalletData::PaypalRedirect(_) + | payments::WalletData::PaypalSdk(_) + | payments::WalletData::SamsungPay(_) + | payments::WalletData::TwintRedirect {} + | payments::WalletData::VippsRedirect {} + | payments::WalletData::TouchNGoRedirect(_) + | payments::WalletData::WeChatPayRedirect(_) + | payments::WalletData::WeChatPayQr(_) + | payments::WalletData::CashappQr(_) + | payments::WalletData::SwishQr(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "Cybersource", + ), + ) + .into()) + } + }, + // If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause. + // This is a fallback implementation in the event of catastrophe. + payments::PaymentMethodData::MandatePayment => { + let connector_mandate_id = + item.router_data.request.connector_mandate_id().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_id", + }, + )?; + Self::try_from((item, connector_mandate_id)) + } + payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ) + .into()) + } + } + } + } + } +} + +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + String, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, connector_mandate_id): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + String, + ), + ) -> Result { + let processing_information = ProcessingInformation::from((item, None)); + let payment_instrument = CybersoucrePaymentInstrument { + id: connector_mandate_id, + }; + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let payment_information = + PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument }); + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceAuthSetupRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> + for CybersourceAuthSetupRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + let payment_information = PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + }); + let client_reference_information = ClientReferenceInformation::from(item); + Ok(Self { + payment_information, + client_reference_information, + }) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ) + .into()) + } + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourcePaymentsCaptureRequest { + processing_information: ProcessingInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourcePaymentsIncrementalAuthorizationRequest { + processing_information: ProcessingInformation, + order_information: OrderInformationIncrementalAuthorization, +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> + for CybersourcePaymentsCaptureRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { + let merchant_defined_information = + item.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + Ok(Self { + processing_information: ProcessingInformation { + capture_options: Some(CaptureOptions { + capture_sequence_number: 1, + total_capture_count: 1, + }), + action_list: None, + action_token_types: None, + authorization_options: None, + capture: None, + commerce_indicator: String::from("internet"), + payment_solution: None, + }, + order_information: OrderInformationWithBill { + amount_details: Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency, + }, + bill_to: None, + }, + client_reference_information: ClientReferenceInformation { + code: Some(item.router_data.connector_request_reference_id.clone()), + }, + merchant_defined_information, + }) + } +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> + for CybersourcePaymentsIncrementalAuthorizationRequest +{ type Error = error_stack::Report; fn try_from( item: &CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>, @@ -473,9 +1168,10 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRout reason: "5".to_owned(), }), }), - commerce_indicator: CybersourceCommerceIndicator::Internet, + commerce_indicator: String::from("internet"), capture: None, capture_options: None, + payment_solution: None, }, order_information: OrderInformationIncrementalAuthorization { amount_details: AdditionalAmount { @@ -487,6 +1183,59 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRout } } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceVoidRequest { + client_reference_information: ClientReferenceInformation, + reversal_information: ReversalInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, + // The connector documentation does not mention the merchantDefinedInformation field for Void requests. But this has been still added because it works! +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReversalInformation { + amount_details: Amount, + reason: String, +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsCancelRouterData>> for CybersourceVoidRequest { + type Error = error_stack::Report; + fn try_from( + value: &CybersourceRouterData<&types::PaymentsCancelRouterData>, + ) -> Result { + let merchant_defined_information = + value.router_data.request.metadata.clone().map(|metadata| { + Vec::::foreign_from(metadata.peek().to_owned()) + }); + Ok(Self { + client_reference_information: ClientReferenceInformation { + code: Some(value.router_data.connector_request_reference_id.clone()), + }, + reversal_information: ReversalInformation { + amount_details: Amount { + total_amount: value.amount.to_owned(), + currency: value.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + }, + )?, + }, + reason: value + .router_data + .request + .cancellation_reason + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Cancellation Reason", + })?, + }, + merchant_defined_information, + }) + } +} + pub struct CybersourceAuthType { pub(super) api_key: Secret, pub(super) merchant_account: Secret, @@ -512,7 +1261,8 @@ impl TryFrom<&types::ConnectorAuthType> for CybersourceAuthType { } } } -#[derive(Debug, Default, Clone, Deserialize)] + +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CybersourcePaymentStatus { Authorized, @@ -522,10 +1272,16 @@ pub enum CybersourcePaymentStatus { Reversed, Pending, Declined, + Rejected, + Challenge, AuthorizedPendingReview, + AuthorizedRiskDeclined, Transmitted, - #[default] - Processing, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + //PartialAuthorized, not being consumed yet. } #[derive(Debug, Clone, Deserialize)] @@ -536,18 +1292,39 @@ pub enum CybersourceIncrementalAuthorizationStatus { AuthorizedPendingReview, } -impl From for enums::AttemptStatus { - fn from(item: CybersourcePaymentStatus) -> Self { - match item { +impl ForeignFrom<(CybersourcePaymentStatus, bool)> for enums::AttemptStatus { + fn foreign_from((status, capture): (CybersourcePaymentStatus, bool)) -> Self { + match status { CybersourcePaymentStatus::Authorized - | CybersourcePaymentStatus::AuthorizedPendingReview => Self::Authorized, + | CybersourcePaymentStatus::AuthorizedPendingReview => { + if capture { + // Because Cybersource will return Payment Status as Authorized even in AutoCapture Payment + Self::Charged + } else { + Self::Authorized + } + } + CybersourcePaymentStatus::Pending => { + if capture { + Self::Charged + } else { + Self::Pending + } + } CybersourcePaymentStatus::Succeeded | CybersourcePaymentStatus::Transmitted => { Self::Charged } CybersourcePaymentStatus::Voided | CybersourcePaymentStatus::Reversed => Self::Voided, - CybersourcePaymentStatus::Failed | CybersourcePaymentStatus::Declined => Self::Failure, - CybersourcePaymentStatus::Processing => Self::Authorizing, - CybersourcePaymentStatus::Pending => Self::Pending, + CybersourcePaymentStatus::Failed + | CybersourcePaymentStatus::Declined + | CybersourcePaymentStatus::AuthorizedRiskDeclined + | CybersourcePaymentStatus::Rejected + | CybersourcePaymentStatus::InvalidRequest + | CybersourcePaymentStatus::ServerError => Self::Failure, + CybersourcePaymentStatus::PendingAuthentication => Self::AuthenticationPending, + CybersourcePaymentStatus::PendingReview | CybersourcePaymentStatus::Challenge => { + Self::Pending + } } } } @@ -574,31 +1351,67 @@ impl From for enums::RefundStatus { } } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourcePaymentsResponse { + ClientReferenceInformation(CybersourceClientReferenceResponse), + ErrorInformation(CybersourceErrorInformationResponse), +} + +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CybersourcePaymentsResponse { +pub struct CybersourceClientReferenceResponse { id: String, status: CybersourcePaymentStatus, - error_information: Option, - client_reference_information: Option, + client_reference_information: ClientReferenceInformation, + processor_information: Option, + risk_information: Option, token_information: Option, + error_information: Option, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CybersourcePaymentsIncrementalAuthorizationResponse { - status: CybersourceIncrementalAuthorizationStatus, - error_information: Option, +pub struct CybersourceErrorInformationResponse { + id: String, + error_information: CybersourceErrorInformation, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthInformationResponse { + access_token: String, + device_data_collection_url: String, + reference_id: String, +} + +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CybersourceSetupMandatesResponse { +pub struct ClientAuthSetupInfoResponse { id: String, - status: CybersourcePaymentStatus, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: CybersourceConsumerAuthInformationResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourceAuthSetupResponse { + ClientAuthSetupInfo(ClientAuthSetupInfoResponse), + ErrorInformation(CybersourceErrorInformationResponse), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourcePaymentsIncrementalAuthorizationResponse { + status: CybersourceIncrementalAuthorizationStatus, error_information: Option, - client_reference_information: Option, - token_information: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourceSetupMandatesResponse { + ClientReferenceInformation(CybersourceClientReferenceResponse), + ErrorInformation(CybersourceErrorInformationResponse), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -607,77 +1420,751 @@ pub struct ClientReferenceInformation { code: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientProcessorInformation { + avs: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Avs { + code: String, + code_raw: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientRiskInformation { + rules: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ClientRiskInformationRules { + name: String, +} + #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceTokenInformation { - instrument_identifier: CybersoucreInstrumentIdentifier, + payment_instrument: CybersoucrePaymentInstrument, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct CybersourceErrorInformation { + reason: Option, + message: Option, +} + +impl + From<( + &CybersourceErrorInformationResponse, + types::ResponseRouterData, + Option, + )> for types::RouterData +{ + fn from( + (error_response, item, transaction_status): ( + &CybersourceErrorInformationResponse, + types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + T, + types::PaymentsResponseData, + >, + Option, + ), + ) -> Self { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + match transaction_status { + Some(status) => Self { + response, + status, + ..item.data + }, + None => Self { + response, + ..item.data + }, + } + } +} + +fn get_error_response_if_failure( + (info_response, status, http_code): ( + &CybersourceClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Option { + if utils::is_payment_failure(status) { + Some(types::ErrorResponse::from(( + &info_response.error_information, + &info_response.risk_information, + http_code, + info_response.id.clone(), + ))) + } else { + None + } +} + +fn get_payment_response( + (info_response, status, http_code): ( + &CybersourceClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Result { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(error), + None => { + let incremental_authorization_allowed = + Some(status == enums::AttemptStatus::Authorized); + let mandate_reference = + info_response + .token_information + .clone() + .map(|token_info| types::MandateReference { + connector_mandate_id: Some(token_info.payment_instrument.id), + payment_method_id: None, + }); + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: None, + mandate_reference, + connector_metadata: info_response + .processor_information + .as_ref() + .map(|processor_information| serde_json::json!({"avs_response": processor_information.avs})), + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed, + }) + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => Ok(Self::from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))), + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourceAuthSetupResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + CybersourceAuthSetupResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + CybersourceAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(services::RedirectForm::CybersourceAuthSetup { + access_token: info_response + .consumer_authentication_information + .access_token, + ddc_url: info_response + .consumer_authentication_information + .device_data_collection_url, + reference_id: info_response + .consumer_authentication_information + .reference_id, + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed: None, + }), + ..item.data + }), + CybersourceAuthSetupResponse::ErrorInformation(error_response) => { + let error_reason = error_response + .error_information + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason; + Ok(Self { + response: Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }), + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthInformationRequest { + return_url: String, + reference_id: String, +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceAuthEnrollmentRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: CybersourceConsumerAuthInformationRequest, + order_information: OrderInformationWithBill, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct CybersourceRedirectionAuthResponse { + pub transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthInformationValidateRequest { + authentication_transaction_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceAuthValidateRequest { + payment_information: PaymentInformation, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: CybersourceConsumerAuthInformationValidateRequest, + order_information: OrderInformation, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum CybersourcePreProcessingRequest { + AuthEnrollment(CybersourceAuthEnrollmentRequest), + AuthValidate(CybersourceAuthValidateRequest), +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> + for CybersourcePreProcessingRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsPreProcessingRouterData>, + ) -> Result { + let client_reference_information = ClientReferenceInformation { + code: Some(item.router_data.connector_request_reference_id.clone()), + }; + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "payment_method_data", + }, + )?; + let payment_information = match payment_method_data { + payments::PaymentMethodData::Card(ccard) => { + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + Ok(PaymentInformation::Cards(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: ccard.card_cvc, + card_type, + }, + })) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + )) + } + }?; + + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + + let amount_details = Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + }, + )?, + }; + + match redirect_response.params { + Some(param) if !param.clone().peek().is_empty() => { + let reference_id = param + .clone() + .peek() + .split_once('=') + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.params.reference_id", + })? + .1 + .to_string(); + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill { + amount_details, + bill_to: Some(bill_to), + }; + Ok(Self::AuthEnrollment(CybersourceAuthEnrollmentRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + CybersourceConsumerAuthInformationRequest { + return_url: item.router_data.request.get_complete_authorize_url()?, + reference_id, + }, + order_information, + })) + } + Some(_) | None => { + let redirect_payload: CybersourceRedirectionAuthResponse = redirect_response + .payload + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + })? + .peek() + .clone() + .parse_value("CybersourceRedirectionAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let order_information = OrderInformation { amount_details }; + Ok(Self::AuthValidate(CybersourceAuthValidateRequest { + payment_information, + client_reference_information, + consumer_authentication_information: + CybersourceConsumerAuthInformationValidateRequest { + authentication_transaction_id: redirect_payload.transaction_id, + }, + order_information, + })) + } + } + } +} + +impl TryFrom<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>> + for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_data", + }, + )?; + match payment_method_data { + payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ) + .into()) + } + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CybersourceAuthEnrollmentStatus { + PendingAuthentication, + AuthenticationSuccessful, + AuthenticationFailed, +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthValidateResponse { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option, + xid: Option, + specification_version: Option, + directory_server_transaction_id: Option, + indicator: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CybersourceThreeDSMetadata { + three_ds_data: CybersourceConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceConsumerAuthInformationEnrollmentResponse { + access_token: Option, + step_up_url: Option, + //Added to segregate the three_ds_data in a separate struct + #[serde(flatten)] + validate_response: CybersourceConsumerAuthValidateResponse, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientAuthCheckInfoResponse { + id: String, + client_reference_information: ClientReferenceInformation, + consumer_authentication_information: CybersourceConsumerAuthInformationEnrollmentResponse, + status: CybersourceAuthEnrollmentStatus, + error_information: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourcePreProcessingResponse { + ClientAuthCheckInfo(Box), + ErrorInformation(CybersourceErrorInformationResponse), +} + +impl From for enums::AttemptStatus { + fn from(item: CybersourceAuthEnrollmentStatus) -> Self { + match item { + CybersourceAuthEnrollmentStatus::PendingAuthentication => Self::AuthenticationPending, + CybersourceAuthEnrollmentStatus::AuthenticationSuccessful => { + Self::AuthenticationSuccessful + } + CybersourceAuthEnrollmentStatus::AuthenticationFailed => Self::AuthenticationFailed, + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourcePreProcessingResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + CybersourcePreProcessingResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + CybersourcePreProcessingResponse::ClientAuthCheckInfo(info_response) => { + let status = enums::AttemptStatus::from(info_response.status); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + let response = Err(types::ErrorResponse::from(( + &info_response.error_information, + &risk_info, + item.http_code, + info_response.id.clone(), + ))); + + Ok(Self { + status, + response, + ..item.data + }) + } else { + let connector_response_reference_id = Some( + info_response + .client_reference_information + .code + .unwrap_or(info_response.id.clone()), + ); + + let redirection_data = match ( + info_response + .consumer_authentication_information + .access_token, + info_response + .consumer_authentication_information + .step_up_url, + ) { + (Some(access_token), Some(step_up_url)) => { + Some(services::RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + }) + } + _ => None, + }; + let three_ds_data = serde_json::to_value( + info_response + .consumer_authentication_information + .validate_response, + ) + .into_report() + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data, + mandate_reference: None, + connector_metadata: Some( + serde_json::json!({"three_ds_data":three_ds_data}), + ), + network_txn_id: None, + connector_response_reference_id, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } + } + CybersourcePreProcessingResponse::ErrorInformation(ref error_response) => { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + }); + Ok(Self { + response, + status: enums::AttemptStatus::AuthenticationFailed, + ..item.data + }) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => Ok(Self::from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))), + } + } } -#[derive(Debug, Clone, Deserialize)] -pub struct CybersourceErrorInformation { - reason: String, - message: String, +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), true)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from((&error_response.clone(), item, None))) + } + } + } } -impl - TryFrom<( - types::ResponseRouterData, - bool, - )> for types::RouterData +impl + TryFrom< + types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( - data: ( - types::ResponseRouterData< - F, - CybersourcePaymentsResponse, - T, - types::PaymentsResponseData, - >, - bool, - ), + item: types::ResponseRouterData< + F, + CybersourcePaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, ) -> Result { - let item = data.0; - let is_capture = data.1; - let mandate_reference = - item.response - .token_information - .map(|token_info| types::MandateReference { - connector_mandate_id: Some(token_info.instrument_identifier.id), - payment_method_id: None, - }); - let status = get_payment_status(is_capture, item.response.status.into()); - Ok(Self { - status, - response: match item.response.error_information { - Some(error) => Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: error.message, - reason: Some(error.reason), - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(item.response.id), - }), - _ => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), - redirection_data: None, - mandate_reference, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(item.response.id)), - incremental_authorization_allowed: Some( - status == enums::AttemptStatus::Authorized, - ), - }), - }, - ..item.data - }) + match item.response { + CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::from((&error_response.clone(), item, None))) + } + } } } @@ -700,49 +2187,74 @@ impl types::PaymentsResponseData, >, ) -> Result { - let mandate_reference = - item.response - .token_information - .map(|token_info| types::MandateReference { - connector_mandate_id: Some(token_info.instrument_identifier.id), - payment_method_id: None, + match item.response { + CybersourceSetupMandatesResponse::ClientReferenceInformation(info_response) => { + let mandate_reference = info_response.token_information.clone().map(|token_info| { + types::MandateReference { + connector_mandate_id: Some(token_info.payment_instrument.id), + payment_method_id: None, + } }); - let mut mandate_status: enums::AttemptStatus = item.response.status.into(); - if matches!(mandate_status, enums::AttemptStatus::Authorized) { - //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. - mandate_status = enums::AttemptStatus::Charged - } - Ok(Self { - status: mandate_status, - response: match item.response.error_information { - Some(error) => Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: error.message, - reason: Some(error.reason), + let mut mandate_status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + if matches!(mandate_status, enums::AttemptStatus::Authorized) { + //In case of zero auth mandates we want to make the payment reach the terminal status so we are converting the authorized status to charged as well. + mandate_status = enums::AttemptStatus::Charged + } + let error_response = + get_error_response_if_failure((&info_response, mandate_status, item.http_code)); + + Ok(Self { + status: mandate_status, + response: match error_response { + Some(error) => Err(error), + None => Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + info_response.id.clone(), + ), + redirection_data: None, + mandate_reference, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id), + ), + incremental_authorization_allowed: Some( + mandate_status == enums::AttemptStatus::Authorized, + ), + }), + }, + ..item.data + }) + } + CybersourceSetupMandatesResponse::ErrorInformation(ref error_response) => { + let error_reason = error_response + .error_information + .message + .to_owned() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_response.error_information.reason.to_owned(); + let response = Err(types::ErrorResponse { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason), status_code: item.http_code, attempt_status: None, - connector_transaction_id: Some(item.response.id), - }), - _ => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.id.clone(), - ), - redirection_data: None, - mandate_reference, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(item.response.id)), - incremental_authorization_allowed: Some( - mandate_status == enums::AttemptStatus::Authorized, - ), - }), - }, - ..item.data - }) + connector_transaction_id: Some(error_response.id.clone()), + }); + Ok(Self { + response, + status: enums::AttemptStatus::Failure, + ..item.data + }) + } + } } } @@ -775,8 +2287,8 @@ impl Some(error) => Ok( types::PaymentsResponseData::IncrementalAuthorizationResponse { status: common_enums::AuthorizationStatus::Failure, - error_code: Some(error.reason), - error_message: Some(error.message), + error_code: error.reason, + error_message: error.message, connector_authorization_id: None, }, ), @@ -794,125 +2306,112 @@ impl } } +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourceTransactionResponse { + ApplicationInformation(CybersourceApplicationInfoResponse), + ErrorInformation(CybersourceErrorInformationResponse), +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CybersourceTransactionResponse { +pub struct CybersourceApplicationInfoResponse { id: String, application_information: ApplicationInformation, client_reference_information: Option, + error_information: Option, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApplicationInformation { status: CybersourcePaymentStatus, } -fn get_payment_status(is_capture: bool, status: enums::AttemptStatus) -> enums::AttemptStatus { - let is_authorized = matches!(status, enums::AttemptStatus::Authorized); - let is_pending = matches!(status, enums::AttemptStatus::Pending); - if is_capture && (is_authorized || is_pending) { - return enums::AttemptStatus::Charged; - } - status -} - -impl - TryFrom<( +impl + TryFrom< types::ResponseRouterData< F, CybersourceTransactionResponse, - T, + types::PaymentsSyncData, types::PaymentsResponseData, >, - bool, - )> for types::RouterData + > for types::RouterData { type Error = error_stack::Report; fn try_from( - data: ( - types::ResponseRouterData< - F, - CybersourceTransactionResponse, - T, - types::PaymentsResponseData, - >, - bool, - ), + item: types::ResponseRouterData< + F, + CybersourceTransactionResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, ) -> Result { - let item = data.0; - let is_capture = data.1; - let status = get_payment_status( - is_capture, - item.response.application_information.status.into(), - ); - Ok(Self { - status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(item.response.id)), - incremental_authorization_allowed: Some(status == enums::AttemptStatus::Authorized), + match item.response { + CybersourceTransactionResponse::ApplicationInformation(app_response) => { + let status = enums::AttemptStatus::foreign_from(( + app_response.application_information.status, + item.data.request.is_auto_capture()?, + )); + let incremental_authorization_allowed = + Some(status == enums::AttemptStatus::Authorized); + let risk_info: Option = None; + if utils::is_payment_failure(status) { + Ok(Self { + response: Err(types::ErrorResponse::from(( + &app_response.error_information, + &risk_info, + item.http_code, + app_response.id.clone(), + ))), + status: enums::AttemptStatus::Failure, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + app_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: app_response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(app_response.id)), + incremental_authorization_allowed, + }), + ..item.data + }) + } + } + CybersourceTransactionResponse::ErrorInformation(error_response) => Ok(Self { + status: item.data.status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + error_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(error_response.id), + incremental_authorization_allowed: None, + }), + ..item.data }), - ..item.data - }) + } } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ErrorResponse { - pub error_information: Option, - pub status: Option, - pub message: Option, - pub reason: Option, - pub details: Option>, -} - -#[derive(Debug, Deserialize, strum::Display)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum Reason { - MissingField, - InvalidData, - DuplicateRequest, - InvalidCard, - AuthAlreadyReversed, - CardTypeNotAccepted, - InvalidMerchantConfiguration, - ProcessorUnavailable, - InvalidAmount, - InvalidCardType, - InvalidPaymentId, - NotSupported, - SystemError, - ServerTimeout, - ServiceTimeout, -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Details { - pub field: String, - pub reason: String, -} - -#[derive(Debug, Deserialize)] -pub struct ErrorInformation { - pub message: String, - pub reason: String, -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceRefundRequest { order_information: OrderInformation, + client_reference_information: ClientReferenceInformation, } impl TryFrom<&CybersourceRouterData<&types::RefundsRouterData>> for CybersourceRefundRequest { @@ -924,37 +2423,81 @@ impl TryFrom<&CybersourceRouterData<&types::RefundsRouterData>> for Cybers order_information: OrderInformation { amount_details: Amount { total_amount: item.amount.clone(), - currency: item.router_data.request.currency.to_string(), + currency: item.router_data.request.currency, }, }, + client_reference_information: ClientReferenceInformation { + code: Some(item.router_data.request.refund_id.clone()), + }, }) } } -impl TryFrom> +impl From for enums::RefundStatus { + fn from(item: CybersourceRefundStatus) -> Self { + match item { + CybersourceRefundStatus::Succeeded | CybersourceRefundStatus::Transmitted => { + Self::Success + } + CybersourceRefundStatus::Failed | CybersourceRefundStatus::Voided => Self::Failure, + CybersourceRefundStatus::Pending => Self::Pending, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CybersourceRefundStatus { + Succeeded, + Transmitted, + Failed, + Pending, + Voided, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceRefundResponse { + id: String, + status: CybersourceRefundStatus, +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { - let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { response: Ok(types::RefundsResponseData { connector_refund_id: item.response.id, - refund_status, + refund_status: enums::RefundStatus::from(item.response.status), }), ..item.data }) } } -impl TryFrom> +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RsyncApplicationInformation { + status: CybersourceRefundStatus, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceRsyncResponse { + id: String, + application_information: RsyncApplicationInformation, +} + +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: types::RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(types::RefundsResponseData { @@ -967,3 +2510,129 @@ impl TryFrom, + pub status: Option, + pub message: Option, + pub reason: Option, + pub details: Option>, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceNotAvailableErrorResponse { + pub errors: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceNotAvailableErrorObject { + #[serde(rename = "type")] + pub error_type: Option, + pub message: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourceServerErrorResponse { + pub status: Option, + pub message: Option, + pub reason: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Reason { + SystemError, + ServerTimeout, + ServiceTimeout, +} + +#[derive(Debug, Deserialize)] +pub struct CybersourceAuthenticationErrorResponse { + pub response: AuthenticationErrorInformation, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum CybersourceErrorResponse { + AuthenticationError(CybersourceAuthenticationErrorResponse), + //If the request resource is not available/exists in cybersource + NotAvailableError(CybersourceNotAvailableErrorResponse), + StandardError(CybersourceStandardErrorResponse), +} + +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Details { + pub field: String, + pub reason: String, +} + +#[derive(Debug, Default, Deserialize)] +pub struct ErrorInformation { + pub message: String, + pub reason: String, +} + +#[derive(Debug, Default, Deserialize)] +pub struct AuthenticationErrorInformation { + pub rmsg: String, +} + +impl + From<( + &Option, + &Option, + u16, + String, + )> for types::ErrorResponse +{ + fn from( + (error_data, risk_information, status_code, transaction_id): ( + &Option, + &Option, + u16, + String, + ), + ) -> Self { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| format!(" , {}", risk_info.name)) + .collect::>() + .join("") + }) + }) + .unwrap_or(Some("".to_string())); + let error_reason = error_data + .clone() + .map(|error_details| { + error_details.message.unwrap_or("".to_string()) + + &avs_message.unwrap_or("".to_string()) + }) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + let error_message = error_data + .clone() + .and_then(|error_details| error_details.reason); + + Self { + code: error_message + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: Some(error_reason.clone()), + status_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(transaction_id.clone()), + } + } +} diff --git a/crates/router/src/connector/dlocal.rs b/crates/router/src/connector/dlocal.rs index 28ae058286f..1e6fc7561ed 100644 --- a/crates/router/src/connector/dlocal.rs +++ b/crates/router/src/connector/dlocal.rs @@ -5,6 +5,7 @@ use std::fmt::Debug; use common_utils::{ crypto::{self, SignMessage}, date_time, + request::RequestContent, }; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -27,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -56,16 +57,11 @@ where connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let dlocal_req = match self.get_request_body(req, connectors)? { - Some(val) => val, - None => types::RequestBody::log_and_get_request_body("".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - }; + let dlocal_req = self.get_request_body(req, connectors)?; let date = date_time::date_as_yyyymmddthhmmssmmmz() .into_report() .change_context(errors::ConnectorError::RequestEncodingFailed)?; - let auth = dlocal::DlocalAuthType::try_from(&req.connector_auth_type)?; let sign_req: String = format!( "{}{}{}", @@ -184,6 +180,20 @@ impl types::PaymentsResponseData, > for Dlocal { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Dlocal".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -213,20 +223,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = dlocal::DlocalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = dlocal::DlocalPaymentsRequest::try_from(&connector_router_data)?; - let dlocal_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_payments_request)) + let connector_req = dlocal::DlocalPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -244,7 +249,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = dlocal::DlocalPaymentsCaptureRequest::try_from(req)?; - let dlocal_payments_capture_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_payments_capture_request)) + ) -> CustomResult { + let connector_req = dlocal::DlocalPaymentsCaptureRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -397,7 +397,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = dlocal::DlocalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = dlocal::DlocalRefundRequest::try_from(&connector_router_data)?; - let dlocal_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_refund_request)) + let connector_req = dlocal::DlocalRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -554,7 +549,7 @@ impl ConnectorIntegration> for DlocalP document: get_doc_from_currency(country.to_string()), }, card: Some(Card { - holder_name: ccard.card_holder_name.clone(), + holder_name: ccard + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), number: ccard.card_number.clone(), cvv: ccard.card_cvc.clone(), expiration_month: ccard.card_exp_month.clone(), diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index 961ef005f2f..28d9eaedf07 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -21,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -152,6 +153,20 @@ impl types::PaymentsResponseData, > for DummyConnector { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented( + "Setup Mandate flow for DummyConnector".to_string(), + ) + .into()) + } } impl @@ -190,14 +205,9 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = transformers::DummyConnectorPaymentsRequest::::try_from(req)?; - let dummmy_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dummmy_payments_request)) + ) -> CustomResult { + let connector_req = transformers::DummyConnectorPaymentsRequest::::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -215,7 +225,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -353,7 +363,7 @@ impl &self, _req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -436,14 +446,9 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = transformers::DummyConnectorRefundRequest::try_from(req)?; - let dummmy_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dummmy_refund_request)) + ) -> CustomResult { + let connector_req = transformers::DummyConnectorRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -458,7 +463,7 @@ impl ConnectorIntegration ConnectorIntegration, } -impl From for DummyConnectorCard { - fn from(value: api_models::payments::Card) -> Self { - Self { - name: value.card_holder_name, +impl TryFrom for DummyConnectorCard { + type Error = error_stack::Report; + fn try_from(value: api_models::payments::Card) -> Result { + Ok(Self { + name: value + .card_holder_name + .unwrap_or(Secret::new("".to_string())), number: value.card_number, expiry_month: value.card_exp_month, expiry_year: value.card_exp_year, cvc: value.card_cvc, - } + }) } } @@ -151,7 +154,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> .payment_method_data { api::PaymentMethodData::Card(ref req_card) => { - Ok(PaymentMethodData::Card(req_card.clone().into())) + Ok(PaymentMethodData::Card(req_card.clone().try_into()?)) } api::PaymentMethodData::Wallet(ref wallet_data) => { Ok(PaymentMethodData::Wallet(wallet_data.clone().try_into()?)) diff --git a/crates/router/src/connector/fiserv.rs b/crates/router/src/connector/fiserv.rs index 28b6d932760..0bd82115627 100644 --- a/crates/router/src/connector/fiserv.rs +++ b/crates/router/src/connector/fiserv.rs @@ -3,6 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -27,7 +28,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -69,9 +70,7 @@ where fiserv::FiservAuthType::try_from(&req.connector_auth_type)?; let mut auth_header = self.get_auth_header(&req.connector_auth_type)?; - let fiserv_req = self - .get_request_body(req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let fiserv_req = self.get_request_body(req, connectors)?; let client_request_id = Uuid::new_v4().to_string(); let hmac = self @@ -213,6 +212,20 @@ impl types::PaymentsResponseData, > for Fiserv { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Fiserv".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Fiserv {} @@ -249,14 +262,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservCancelRequest::try_from(req)?; - let fiserv_payments_cancel_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_cancel_request)) + ) -> CustomResult { + let connector_req = fiserv::FiservCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -270,7 +278,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservSyncRequest::try_from(req)?; - let fiserv_payments_sync_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_sync_request)) + ) -> CustomResult { + let connector_req = fiserv::FiservSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -359,7 +362,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = fiserv::FiservCaptureRequest::try_from(&router_obj)?; - let fiserv_payments_capture_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_capture_request)) + let connector_req = fiserv::FiservCaptureRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -443,7 +441,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = fiserv::FiservPaymentsRequest::try_from(&router_obj)?; - let fiserv_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_request)) + let connector_req = fiserv::FiservPaymentsRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -559,7 +552,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = fiserv::FiservRefundRequest::try_from(&router_obj)?; - let fiserv_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_refund_request)) + let connector_req = fiserv::FiservRefundRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -651,7 +639,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservSyncRequest::try_from(req)?; - let fiserv_sync_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_sync_request)) + ) -> CustomResult { + let connector_req = fiserv::FiservSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -734,7 +717,7 @@ impl ConnectorIntegration for Forte { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Forte".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -203,14 +218,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::FortePaymentsRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -228,7 +238,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteCaptureRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -385,7 +390,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteCancelRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -469,7 +469,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteRefundRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -553,7 +548,7 @@ impl ConnectorIntegration for ForteCardType { utils::CardIssuer::Visa => Ok(Self::Visa), utils::CardIssuer::DinersClub => Ok(Self::DinersClub), utils::CardIssuer::JCB => Ok(Self::Jcb), - _ => Err(errors::ConnectorError::NotSupported { - message: issuer.to_string(), - connector: "Forte", - } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Forte"), + ) .into()), } } @@ -67,10 +66,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for FortePaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { if item.request.currency != enums::Currency::USD { - Err(errors::ConnectorError::NotSupported { - message: item.request.currency.to_string(), - connector: "Forte", - })? + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Forte"), + ))? } match item.request.payment_method_data { api_models::payments::PaymentMethodData::Card(ref ccard) => { @@ -82,7 +80,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for FortePaymentsRequest { let address = item.get_billing_address()?; let card = Card { card_type, - name_on_card: ccard.card_holder_name.clone(), + name_on_card: ccard + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), account_number: ccard.card_number.clone(), expire_month: ccard.card_exp_month.clone(), expire_year: ccard.card_exp_year.clone(), @@ -114,10 +115,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for FortePaymentsRequest { | api_models::payments::PaymentMethodData::Voucher(_) | api_models::payments::PaymentMethodData::GiftCard(_) | api_models::payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotSupported { - message: utils::SELECTED_PAYMENT_METHOD.to_string(), - connector: "Forte", - })? + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Forte"), + ))? } } } diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 39452e53df1..76954217eef 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -1,10 +1,11 @@ mod requests; +use super::utils as connector_utils; mod response; pub mod transformers; use std::fmt::Debug; -use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -20,7 +21,6 @@ use self::{ use super::utils::RefundsRequestData; use crate::{ configs::settings, - connector::{utils as connector_utils, utils as conn_utils}, core::{ errors::{self, CustomResult}, payments, @@ -36,7 +36,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, ErrorResponse, }, - utils::{self, crypto, BytesExt}, + utils::{crypto, BytesExt}, }; #[derive(Debug, Clone)] @@ -167,10 +167,8 @@ impl &self, _req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let globalpay_req = types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + ) -> CustomResult { + Ok(RequestContent::Json(Box::new(serde_json::json!({})))) } fn build_request( @@ -188,7 +186,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -265,7 +263,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = GlobalpayRefreshTokenRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + ) -> CustomResult { + let connector_req = GlobalpayRefreshTokenRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -347,6 +340,20 @@ impl types::PaymentsResponseData, > for Globalpay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Globalpay".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Globalpay {} @@ -389,7 +396,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = requests::GlobalpayCancelRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + ) -> CustomResult { + let connector_req = requests::GlobalpayCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -551,14 +553,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = requests::GlobalpayCaptureRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + ) -> CustomResult { + let connector_req = requests::GlobalpayCaptureRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -574,7 +571,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = GlobalpayPaymentsRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + ) -> CustomResult { + let connector_req = GlobalpayPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -668,7 +660,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = requests::GlobalpayRefundRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -758,7 +745,7 @@ impl ConnectorIntegration, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let signature = conn_utils::get_header_key_value("x-gp-signature", request.headers)?; + let signature = connector_utils::get_header_key_value("x-gp-signature", request.headers)?; Ok(signature.as_bytes().to_vec()) } diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index 9cef564b379..3124501bd6e 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -385,7 +385,7 @@ fn get_payment_method_data( api::PaymentMethodData::Card(ccard) => Ok(PaymentMethodData::Card(requests::Card { number: ccard.card_number.clone(), expiry_month: ccard.card_exp_month.clone(), - expiry_year: ccard.get_card_expiry_year_2_digit(), + expiry_year: ccard.get_card_expiry_year_2_digit()?, cvv: ccard.card_cvc.clone(), account_type: None, authcode: None, diff --git a/crates/router/src/connector/globepay.rs b/crates/router/src/connector/globepay.rs index 79704bb9530..8f8b01de8ef 100644 --- a/crates/router/src/connector/globepay.rs +++ b/crates/router/src/connector/globepay.rs @@ -2,7 +2,10 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::crypto::{self, GenerateDigest}; +use common_utils::{ + crypto::{self, GenerateDigest}, + request::RequestContent, +}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use hex::encode; @@ -22,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -147,6 +150,20 @@ impl types::PaymentsResponseData, > for Globepay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Globepay".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -190,14 +207,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = globepay::GlobepayPaymentsRequest::try_from(req)?; - let globepay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globepay_req)) + ) -> CustomResult { + let connector_req = globepay::GlobepayPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -215,7 +227,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = globepay::GlobepayRefundRequest::try_from(req)?; - let globepay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -392,7 +399,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessCustomerRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + ) -> CustomResult { + let connector_req = gocardless::GocardlessCustomerRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -180,7 +175,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -253,14 +248,9 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessBankAccountRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + ) -> CustomResult { + let connector_req = gocardless::GocardlessBankAccountRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -274,7 +264,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -374,14 +364,9 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessMandateRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + ) -> CustomResult { + let connector_req = gocardless::GocardlessMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -397,7 +382,7 @@ impl .url(&types::SetupMandateType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -458,20 +443,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = gocardless::GocardlessPaymentsRequest::try_from(&connector_router_data)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + let connector_req = + gocardless::GocardlessPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -489,7 +470,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = gocardless::GocardlessRefundRequest::try_from(&connector_router_data)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + let connector_req = gocardless::GocardlessRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -654,7 +630,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = helcim::HelcimVerifyRequest::try_from(req)?; - let helcim_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::SetupMandateType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( - self, req, connectors, - )?) - .build(), - )) + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Helcim".to_string()) + .into(), + ) + + // Ok(Some( + // services::RequestBuilder::new() + // .method(services::Method::Post) + // .url(&types::SetupMandateType::get_url(self, req, connectors)?) + // .attach_default_headers() + // .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + // .set_body(types::SetupMandateType::get_request_body( + // self, req, connectors, + // )?) + // .build(), + // )) } fn handle_response( &self, @@ -275,20 +276,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = helcim::HelcimRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = helcim::HelcimPaymentsRequest::try_from(&connector_router_data)?; - let helcim_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + let connector_req = helcim::HelcimPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -306,7 +302,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = helcim::HelcimRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -457,12 +453,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -478,7 +469,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = helcim::HelcimVoidRequest::try_from(req)?; - let helcim_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + ) -> CustomResult { + let connector_req = helcim::HelcimVoidRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -557,7 +543,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = helcim::HelcimRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = helcim::HelcimRefundRequest::try_from(&connector_router_data)?; - let helcim_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + let connector_req = helcim::HelcimRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -642,7 +623,7 @@ impl ConnectorIntegration } } +pub fn check_currency( + currency: types::storage::enums::Currency, +) -> Result { + if currency == types::storage::enums::Currency::USD { + Ok(currency) + } else { + Err(errors::ConnectorError::NotSupported { + message: format!("currency {currency} is not supported for this merchant account"), + connector: "Helcim", + })? + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct HelcimVerifyRequest { @@ -62,6 +75,7 @@ pub struct HelcimPaymentsRequest { currency: enums::Currency, ip_address: Secret, card_data: HelcimCard, + invoice: HelcimInvoice, billing_address: HelcimBillingAddress, //The ecommerce field is an optional field in Connector Helcim. //Setting the ecommerce field to true activates the Helcim Fraud Defender. @@ -83,6 +97,22 @@ pub struct HelcimBillingAddress { email: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimInvoice { + invoice_number: String, + line_items: Vec, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimLineItems { + description: String, + quantity: u8, + price: f64, + total: f64, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct HelcimCard { @@ -96,7 +126,8 @@ impl TryFrom<(&types::SetupMandateRouterData, &api::Card)> for HelcimVerifyReque fn try_from(value: (&types::SetupMandateRouterData, &api::Card)) -> Result { let (item, req_card) = value; let card_data = HelcimCard { - card_expiry: req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + card_expiry: req_card + .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?, card_number: req_card.card_number.clone(), card_c_v_v: req_card.card_cvc.clone(), }; @@ -111,9 +142,9 @@ impl TryFrom<(&types::SetupMandateRouterData, &api::Card)> for HelcimVerifyReque email: item.request.email.clone(), }; let ip_address = item.request.get_browser_info()?.get_ip_address()?; - + let currency = check_currency(item.request.currency)?; Ok(Self { - currency: item.request.currency, + currency, ip_address, card_data, billing_address, @@ -143,10 +174,9 @@ impl TryFrom<&types::SetupMandateRouterData> for HelcimVerifyRequest { | api_models::payments::PaymentMethodData::Voucher(_) | api_models::payments::PaymentMethodData::GiftCard(_) | api_models::payments::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotSupported { - message: format!("{:?}", item.request.payment_method_data), - connector: "Helcim", - })? + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Helcim"), + ))? } } } @@ -167,7 +197,8 @@ impl ) -> Result { let (item, req_card) = value; let card_data = HelcimCard { - card_expiry: req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + card_expiry: req_card + .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?, card_number: req_card.card_number.clone(), card_c_v_v: req_card.card_cvc.clone(), }; @@ -192,11 +223,30 @@ impl .request .get_browser_info()? .get_ip_address()?; + let line_items = vec![ + (HelcimLineItems { + description: item + .router_data + .description + .clone() + .unwrap_or("No Description".to_string()), + // By default quantity is set to 1 and price and total is set to amount because these three fields are required to generate an invoice. + quantity: 1, + price: item.amount, + total: item.amount, + }), + ]; + let invoice = HelcimInvoice { + invoice_number: item.router_data.connector_request_reference_id.clone(), + line_items, + }; + let currency = check_currency(item.router_data.request.currency)?; Ok(Self { - amount: item.amount.to_owned(), - currency: item.router_data.request.currency, + amount: item.amount, + currency, ip_address, card_data, + invoice, billing_address, ecommerce: None, }) @@ -225,10 +275,9 @@ impl TryFrom<&HelcimRouterData<&types::PaymentsAuthorizeRouterData>> for HelcimP | api_models::payments::PaymentMethodData::Upi(_) | api_models::payments::PaymentMethodData::Voucher(_) | api_models::payments::PaymentMethodData::GiftCard(_) - | api::PaymentMethodData::CardToken(_) => Err(errors::ConnectorError::NotSupported { - message: format!("{:?}", item.router_data.request.payment_method_data), - connector: "Helcim", - })?, + | api::PaymentMethodData::CardToken(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Helcim"), + ))?, } } } @@ -295,6 +344,7 @@ impl From for enums::AttemptStatus { pub struct HelcimPaymentsResponse { status: HelcimPaymentStatus, transaction_id: u64, + invoice_number: Option, #[serde(rename = "type")] transaction_type: HelcimTransactionType, } @@ -327,7 +377,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, }), status: enums::AttemptStatus::from(item.response), @@ -382,7 +432,7 @@ impl mandate_reference: None, connector_metadata, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, }), status: enums::AttemptStatus::from(item.response), @@ -441,7 +491,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, }), status: enums::AttemptStatus::from(item.response), @@ -528,7 +578,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, }), status: enums::AttemptStatus::from(item.response), @@ -591,7 +641,7 @@ impl mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, }), status: enums::AttemptStatus::from(item.response), diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index e4e505b5cd4..0c156ef08b0 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -3,7 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as iatapay; @@ -25,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{BytesExt, Encode}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -180,14 +180,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = iatapay::IatapayAuthUpdateRequest::try_from(req)?; - let iatapay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + ) -> CustomResult { + let connector_req = iatapay::IatapayAuthUpdateRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -201,7 +196,7 @@ impl ConnectorIntegration for Iatapay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Iatapay".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -282,20 +291,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = iatapay::IatapayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = iatapay::IatapayPaymentsRequest::try_from(&connector_router_data)?; - let iatapay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + let connector_req = iatapay::IatapayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -313,7 +317,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = iatapay::IatapayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, - req.request.payment_amount, + req.request.refund_amount, req, ))?; - let req_obj = iatapay::IatapayRefundRequest::try_from(&connector_router_data)?; - let iatapay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + let connector_req = iatapay::IatapayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -499,7 +498,7 @@ impl ConnectorIntegration; + // Every access token will be valid for 5 minutes. It contains grant_type and scope for different type of access, but for our usecases it should be only 'client_credentials' and 'payment' resp(as per doc) for all type of api call. #[derive(Debug, Serialize)] pub struct IatapayAuthUpdateRequest { @@ -257,53 +261,85 @@ pub struct IatapayPaymentsResponse { pub bank_transfer_description: Option, pub checkout_methods: Option, pub failure_code: Option, + pub failure_details: Option, +} + +fn get_iatpay_response( + response: IatapayPaymentsResponse, + status_code: u16, +) -> CustomResult< + ( + enums::AttemptStatus, + Option, + types::PaymentsResponseData, + ), + errors::ConnectorError, +> { + let status = enums::AttemptStatus::from(response.status); + let error = if status == enums::AttemptStatus::Failure { + Some(types::ErrorResponse { + code: response + .failure_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .failure_details + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.failure_details, + status_code, + attempt_status: Some(status), + connector_transaction_id: response.iata_payment_id.clone(), + }) + } else { + None + }; + let form_fields = HashMap::new(); + let id = match response.iata_payment_id.clone() { + Some(s) => types::ResponseId::ConnectorTransactionId(s), + None => types::ResponseId::NoResponseId, + }; + let connector_response_reference_id = response.merchant_payment_id.or(response.iata_payment_id); + + let payment_response_data = response.checkout_methods.map_or( + types::PaymentsResponseData::TransactionResponse { + resource_id: id.clone(), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: connector_response_reference_id.clone(), + incremental_authorization_allowed: None, + }, + |checkout_methods| types::PaymentsResponseData::TransactionResponse { + resource_id: id, + redirection_data: Some(services::RedirectForm::Form { + endpoint: checkout_methods.redirect.redirect_url, + method: services::Method::Get, + form_fields, + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: connector_response_reference_id.clone(), + incremental_authorization_allowed: None, + }, + ); + Ok((status, error, payment_response_data)) } impl TryFrom> for types::RouterData { - type Error = error_stack::Report; + type Error = Error; fn try_from( item: types::ResponseRouterData, ) -> Result { - let form_fields = HashMap::new(); - let id = match item.response.iata_payment_id.clone() { - Some(s) => types::ResponseId::ConnectorTransactionId(s), - None => types::ResponseId::NoResponseId, - }; - let connector_response_reference_id = item - .response - .merchant_payment_id - .or(item.response.iata_payment_id); + let (status, error, payment_response_data) = + get_iatpay_response(item.response, item.http_code)?; Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: item.response.checkout_methods.map_or( - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: id.clone(), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: connector_response_reference_id.clone(), - incremental_authorization_allowed: None, - }), - |checkout_methods| { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: id, - redirection_data: Some(services::RedirectForm::Form { - endpoint: checkout_methods.redirect.redirect_url, - method: services::Method::Get, - form_fields, - }), - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: connector_response_reference_id.clone(), - incremental_authorization_allowed: None, - }) - }, - ), + status, + response: error.map_or_else(|| Ok(payment_response_data), Err), ..item.data }) } diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 91eaf94c01e..5361a636826 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; use api_models::payments as api_payments; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use transformers as klarna; @@ -21,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon}, storage::enums as storage_enums, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -156,15 +157,10 @@ impl &self, req: &types::PaymentsSessionRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = klarna::KlarnaSessionRequest::try_from(req)?; // encode only for for urlencoded things. - let klarna_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(klarna_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -180,7 +176,7 @@ impl .headers(types::PaymentsSessionType::get_headers( self, req, connectors, )?) - .body(types::PaymentsSessionType::get_request_body( + .set_body(types::PaymentsSessionType::get_request_body( self, req, connectors, )?) .build(), @@ -222,6 +218,20 @@ impl > for Klarna { // Not Implemented(R) + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Klarna".to_string()) + .into(), + ) + } } impl @@ -417,7 +427,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = klarna::KlarnaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -425,12 +435,7 @@ impl req, ))?; let connector_req = klarna::KlarnaPaymentsRequest::try_from(&connector_router_data)?; - let klarna_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(klarna_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -448,7 +453,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/mollie.rs b/crates/router/src/connector/mollie.rs index 4e610003de3..cee4225d861 100644 --- a/crates/router/src/connector/mollie.rs +++ b/crates/router/src/connector/mollie.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as mollie; @@ -24,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -152,14 +153,9 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = mollie::MollieCardTokenRequest::try_from(req)?; - let mollie_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -172,7 +168,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -213,6 +209,20 @@ impl types::PaymentsResponseData, > for Mollie { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Mollie".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -238,20 +248,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = mollie::MollieRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = mollie::MolliePaymentsRequest::try_from(&router_obj)?; - let mollie_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + let connector_req = mollie::MolliePaymentsRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -269,7 +274,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = mollie::MollieRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = mollie::MollieRefundRequest::try_from(&router_obj)?; - let mollie_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + let connector_req = mollie::MollieRefundRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -463,7 +463,7 @@ impl ConnectorIntegration for MollieCardTokenRequest { match item.request.payment_method_data.clone() { api_models::payments::PaymentMethodData::Card(ccard) => { let auth = MollieAuthType::try_from(&item.connector_auth_type)?; - let card_holder = ccard.card_holder_name.clone(); + let card_holder = ccard + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())); let card_number = ccard.card_number.clone(); let card_expiry_date = - ccard.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned()); + ccard.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?; let card_cvv = ccard.card_cvc; let locale = item.request.get_browser_info()?.get_language()?; let testmode = diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 6079d512d81..8290a00661c 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; use transformers as multisafepay; @@ -21,7 +22,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -129,6 +130,20 @@ impl types::PaymentsResponseData, > for Multisafepay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented( + "Setup Mandate flow for Multisafepay".to_string(), + ) + .into()) + } } impl api::PaymentVoid for Multisafepay {} @@ -264,20 +279,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = multisafepay::MultisafepayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = multisafepay::MultisafepayPaymentsRequest::try_from(&connector_router_data)?; - let multisafepay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(multisafepay_req)) + let connector_req = + multisafepay::MultisafepayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -295,7 +306,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = multisafepay::MultisafepayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = multisafepay::MultisafepayRefundRequest::try_from(&connector_req)?; + let connector_req = multisafepay::MultisafepayRefundRequest::try_from(&connector_req)?; - let multisafepay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(multisafepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -397,7 +403,7 @@ impl ConnectorIntegration> card_expiry_date: Some( (format!( "{}{}", - ccard.get_card_expiry_year_2_digit().expose(), + ccard.get_card_expiry_year_2_digit()?.expose(), ccard.card_exp_month.clone().expose() )) .parse::() diff --git a/crates/router/src/connector/nexinets.rs b/crates/router/src/connector/nexinets.rs index dc743385db1..1f4a7f41f66 100644 --- a/crates/router/src/connector/nexinets.rs +++ b/crates/router/src/connector/nexinets.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use transformers as nexinets; @@ -24,7 +25,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -168,6 +169,20 @@ impl types::PaymentsResponseData, > for Nexinets { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Nexinets".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -202,14 +217,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nexinets::NexinetsPaymentsRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + ) -> CustomResult { + let connector_req = nexinets::NexinetsPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -227,7 +237,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -390,7 +395,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -474,7 +474,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = nexinets::NexinetsRefundRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + ) -> CustomResult { + let connector_req = nexinets::NexinetsRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -562,7 +557,7 @@ impl ConnectorIntegration CardDataDetails::PaymentInstrument(Box::new(PaymentInstrument { payment_instrument_id: item.request.connector_mandate_id(), })), - _ => CardDataDetails::CardDetails(Box::new(get_card_details(card))), + _ => CardDataDetails::CardDetails(Box::new(get_card_details(card)?)), }; let cof_contract = Some(CofContract { recurring_type: RecurringType::Unscheduled, @@ -651,7 +651,7 @@ fn get_card_data( (card_data, cof_contract) } false => ( - CardDataDetails::CardDetails(Box::new(get_card_details(card))), + CardDataDetails::CardDetails(Box::new(get_card_details(card)?)), None, ), }; @@ -677,13 +677,15 @@ fn get_applepay_details( }) } -fn get_card_details(req_card: &api_models::payments::Card) -> CardDetails { - CardDetails { +fn get_card_details( + req_card: &api_models::payments::Card, +) -> Result { + Ok(CardDetails { card_number: req_card.card_number.clone(), expiry_month: req_card.card_exp_month.clone(), - expiry_year: req_card.get_card_expiry_year_2_digit(), + expiry_year: req_card.get_card_expiry_year_2_digit()?, verification: req_card.card_cvc.clone(), - } + }) } fn get_wallet_details( diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index eaede225d38..0550908649f 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -2,23 +2,23 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; +use regex::Regex; use transformers as nmi; -use self::transformers::NmiCaptureRequest; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::utils as connector_utils, core::errors::{self, CustomResult}, services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignFrom, ErrorResponse, }, - utils, }; #[derive(Clone, Debug)] @@ -75,10 +75,12 @@ impl ConnectorCommon for Nmi { .parse_struct("StandardResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; Ok(ErrorResponse { - message: response.responsetext, + message: response.responsetext.to_owned(), status_code: res.status_code, - reason: None, - ..Default::default() + reason: Some(response.responsetext), + code: response.response_code, + attempt_status: None, + connector_transaction_id: Some(response.transactionid), }) } } @@ -96,6 +98,14 @@ impl ConnectorValidation for Nmi { ), } } + + fn validate_psync_reference_id( + &self, + _data: &types::PaymentsSyncRouterData, + ) -> CustomResult<(), errors::ConnectorError> { + // in case we dont have transaction id, we can make psync using attempt id + Ok(()) + } } impl @@ -144,39 +154,120 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiPaymentsRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( &self, - req: &types::SetupMandateRouterData, + _req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Nmi".to_string()).into()) + + // Ok(Some( + // services::RequestBuilder::new() + // .method(services::Method::Post) + // .url(&types::SetupMandateType::get_url(self, req, connectors)?) + // .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + // .set_body(types::SetupMandateType::get_request_body( + // self, req, connectors, + // )?) + // .build(), + // )) + } + + fn handle_response( + &self, + data: &types::SetupMandateRouterData, + res: types::Response, + ) -> CustomResult { + let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl api::PaymentsPreProcessing for Nmi {} + +impl + ConnectorIntegration< + api::PreProcessing, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + > for Nmi +{ + fn get_headers( + &self, + req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsPreProcessingRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}api/transact.php", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsPreProcessingRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = nmi::NmiVaultRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PaymentsPreProcessingRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( + let req = Some( services::RequestBuilder::new() .method(services::Method::Post) - .url(&types::SetupMandateType::get_url(self, req, connectors)?) - .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), - )) + ); + Ok(req) } fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &types::PaymentsPreProcessingRouterData, res: types::Response, - ) -> CustomResult { - let response: nmi::StandardResponse = serde_urlencoded::from_bytes(&res.response) + ) -> CustomResult { + let response: nmi::NmiVaultResponse = serde_urlencoded::from_bytes(&res.response) .into_report() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { @@ -217,7 +308,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -225,12 +316,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -247,7 +333,7 @@ impl ConnectorIntegration for Nmi +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}api/transact.php", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = nmi::NmiRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = nmi::NmiCompleteRequest::try_from(&connector_router_data)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: types::Response, + ) -> CustomResult { + let response: nmi::NmiCompleteResponse = serde_urlencoded::from_bytes(&res.response) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + impl ConnectorIntegration for Nmi { @@ -300,14 +471,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -320,7 +486,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -378,12 +544,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -398,7 +559,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiCancelRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -471,7 +627,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -530,12 +686,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -550,7 +701,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -621,7 +767,7 @@ impl ConnectorIntegration, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let sig_header = + connector_utils::get_header_key_value("webhook-signature", request.headers)?; + + let regex_pattern = r"t=(.*),s=(.*)"; + + if let Some(captures) = Regex::new(regex_pattern) + .into_report() + .change_context(errors::ConnectorError::WebhookSignatureNotFound)? + .captures(sig_header) + { + let signature = captures + .get(1) + .ok_or(errors::ConnectorError::WebhookSignatureNotFound) + .into_report()? + .as_str(); + return Ok(signature.as_bytes().to_vec()); + } + + Err(errors::ConnectorError::WebhookSignatureNotFound).into_report() + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let sig_header = + connector_utils::get_header_key_value("webhook-signature", request.headers)?; + + let regex_pattern = r"t=(.*),s=(.*)"; + + if let Some(captures) = Regex::new(regex_pattern) + .into_report() + .change_context(errors::ConnectorError::WebhookSignatureNotFound)? + .captures(sig_header) + { + let nonce = captures + .get(0) + .ok_or(errors::ConnectorError::WebhookSignatureNotFound) + .into_report()? + .as_str(); + + let message = format!("{}.{}", nonce, String::from_utf8_lossy(request.body)); + + return Ok(message.into_bytes()); + } + Err(errors::ConnectorError::WebhookSignatureNotFound).into_report() + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + let reference_body: nmi::NmiWebhookObjectReference = request + .body + .parse_struct("nmi NmiWebhookObjectReference") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + + let object_reference_id = match reference_body.event_body.action.action_type { + nmi::NmiActionType::Sale => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Auth => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Capture => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Void => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + reference_body.event_body.order_id, + ), + ), + nmi::NmiActionType::Refund => api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::RefundId(reference_body.event_body.order_id), + ), + _ => Err(errors::ConnectorError::WebhooksNotImplemented).into_report()?, + }; + + Ok(object_reference_id) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + let event_type_body: nmi::NmiWebhookEventBody = request + .body + .parse_struct("nmi NmiWebhookEventType") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + + Ok(api::IncomingWebhookEvent::foreign_from( + event_type_body.event_type, + )) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + let webhook_body: nmi::NmiWebhookBody = request + .body + .parse_struct("nmi NmiWebhookBody") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + + match webhook_body.event_body.action.action_type { + nmi::NmiActionType::Sale + | nmi::NmiActionType::Auth + | nmi::NmiActionType::Capture + | nmi::NmiActionType::Void + | nmi::NmiActionType::Credit => { + Ok(Box::new(nmi::SyncResponse::try_from(&webhook_body)?)) + } + nmi::NmiActionType::Refund => Ok(Box::new(webhook_body)), + } } } diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 35c0e102020..fcf35bfbe37 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -1,12 +1,17 @@ +use api_models::webhooks; use cards::CardNumber; -use common_utils::ext_traits::XmlExt; +use common_utils::{errors::CustomResult, ext_traits::XmlExt}; use error_stack::{IntoReport, Report, ResultExt}; -use masking::Secret; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, PaymentsAuthorizeRequestData}, + connector::utils::{ + self, AddressDetailsData, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, RouterData, + }, core::errors, + services, types::{self, api, storage::enums, transformers::ForeignFrom, ConnectorAuthType}, }; @@ -25,17 +30,22 @@ pub enum TransactionType { pub struct NmiAuthType { pub(super) api_key: Secret, + pub(super) public_key: Option>, } impl TryFrom<&ConnectorAuthType> for NmiAuthType { type Error = Error; fn try_from(auth_type: &ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type { - Ok(Self { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { api_key: api_key.to_owned(), - }) - } else { - Err(errors::ConnectorError::FailedToObtainAuthType.into()) + public_key: None, + }), + types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + api_key: api_key.to_owned(), + public_key: Some(key1.to_owned()), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } @@ -71,6 +81,319 @@ impl } } +#[derive(Debug, Serialize)] +pub struct NmiVaultRequest { + security_key: Secret, + ccnumber: CardNumber, + ccexp: Secret, + cvv: Secret, + first_name: Secret, + last_name: Secret, + customer_vault: CustomerAction, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum CustomerAction { + AddCustomer, + UpdateCustomer, +} + +impl TryFrom<&types::PaymentsPreProcessingRouterData> for NmiVaultRequest { + type Error = Error; + fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result { + let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?; + let (ccnumber, ccexp, cvv) = get_card_details(item.request.payment_method_data.clone())?; + let billing_details = item.get_billing_address()?; + + Ok(Self { + security_key: auth_type.api_key, + ccnumber, + ccexp, + cvv, + first_name: billing_details.get_first_name()?.to_owned(), + last_name: billing_details.get_last_name()?.to_owned(), + customer_vault: CustomerAction::AddCustomer, + }) + } +} + +fn get_card_details( + payment_method_data: Option, +) -> CustomResult<(CardNumber, Secret, Secret), errors::ConnectorError> { + match payment_method_data { + Some(api::PaymentMethodData::Card(ref card_details)) => Ok(( + card_details.card_number.clone(), + utils::CardData::get_card_expiry_month_year_2_digit_with_delimiter( + card_details, + "".to_string(), + )?, + card_details.card_cvc.clone(), + )), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Nmi"), + )) + .into_report(), + } +} + +#[derive(Debug, Deserialize)] +pub struct NmiVaultResponse { + pub response: Response, + pub responsetext: String, + pub customer_vault_id: Option, + pub response_code: String, + pub transactionid: String, +} + +impl + TryFrom< + types::ResponseRouterData< + api::PreProcessing, + NmiVaultResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + > for types::PaymentsPreProcessingRouterData +{ + type Error = Error; + fn try_from( + item: types::ResponseRouterData< + api::PreProcessing, + NmiVaultResponse, + types::PaymentsPreProcessingData, + types::PaymentsResponseData, + >, + ) -> Result { + let auth_type: NmiAuthType = (&item.data.connector_auth_type).try_into()?; + let amount_data = + item.data + .request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?; + let currency_data = + item.data + .request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?; + let (response, status) = match item.response.response { + Response::Approved => ( + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(services::RedirectForm::Nmi { + amount: utils::to_currency_base_unit_asf64( + amount_data, + currency_data.to_owned(), + )? + .to_string(), + currency: currency_data, + customer_vault_id: item.response.customer_vault_id.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "customer_vault_id", + }, + )?, + public_key: auth_type.public_key.ok_or( + errors::ConnectorError::InvalidConnectorConfig { + config: "public_key", + }, + )?, + order_id: item.data.connector_request_reference_id.clone(), + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.transactionid), + incremental_authorization_allowed: None, + }), + enums::AttemptStatus::AuthenticationPending, + ), + Response::Declined | Response::Error => ( + Err(types::ErrorResponse { + code: item.response.response_code, + message: item.response.responsetext.to_owned(), + reason: Some(item.response.responsetext), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.transactionid), + }), + enums::AttemptStatus::Failure, + ), + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} + +#[derive(Debug, Serialize)] +pub struct NmiCompleteRequest { + amount: f64, + #[serde(rename = "type")] + transaction_type: TransactionType, + security_key: Secret, + orderid: String, + ccnumber: CardNumber, + ccexp: Secret, + cardholder_auth: CardHolderAuthType, + cavv: String, + xid: String, + three_ds_version: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CardHolderAuthType { + Verified, + Attempted, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum ThreeDsVersion { + #[serde(rename = "2.0.0")] + VersionTwo, + #[serde(rename = "2.1.0")] + VersionTwoPointOne, + #[serde(rename = "2.2.0")] + VersionTwoPointTwo, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NmiRedirectResponseData { + cavv: String, + xid: String, + card_holder_auth: CardHolderAuthType, + three_ds_version: Option, + order_id: String, +} + +impl TryFrom<&NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for NmiCompleteRequest { + type Error = Error; + fn try_from( + item: &NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let transaction_type = match item.router_data.request.is_auto_capture()? { + true => TransactionType::Sale, + false => TransactionType::Auth, + }; + let auth_type: NmiAuthType = (&item.router_data.connector_auth_type).try_into()?; + let payload_data = item + .router_data + .request + .get_redirect_response_payload()? + .expose(); + + let three_ds_data: NmiRedirectResponseData = serde_json::from_value(payload_data) + .into_report() + .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "three_ds_data", + })?; + + let (ccnumber, ccexp, ..) = + get_card_details(item.router_data.request.payment_method_data.clone())?; + + Ok(Self { + amount: item.amount, + transaction_type, + security_key: auth_type.api_key, + orderid: three_ds_data.order_id, + ccnumber, + ccexp, + cardholder_auth: three_ds_data.card_holder_auth, + cavv: three_ds_data.cavv, + xid: three_ds_data.xid, + three_ds_version: three_ds_data.three_ds_version, + }) + } +} + +#[derive(Debug, Deserialize)] +pub struct NmiCompleteResponse { + pub response: Response, + pub responsetext: String, + pub authcode: Option, + pub transactionid: String, + pub avsresponse: Option, + pub cvvresponse: Option, + pub orderid: String, + pub response_code: String, +} + +impl + TryFrom< + types::ResponseRouterData< + api::CompleteAuthorize, + NmiCompleteResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::PaymentsCompleteAuthorizeRouterData +{ + type Error = Error; + fn try_from( + item: types::ResponseRouterData< + api::CompleteAuthorize, + NmiCompleteResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + let (response, status) = match item.response.response { + Response::Approved => ( + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.transactionid, + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.orderid), + incremental_authorization_allowed: None, + }), + if let Some(diesel_models::enums::CaptureMethod::Automatic) = + item.data.request.capture_method + { + enums::AttemptStatus::CaptureInitiated + } else { + enums::AttemptStatus::Authorizing + }, + ), + Response::Declined | Response::Error => ( + Err(types::ErrorResponse::foreign_from(( + item.response, + item.http_code, + ))), + enums::AttemptStatus::Failure, + ), + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} + +impl ForeignFrom<(NmiCompleteResponse, u16)> for types::ErrorResponse { + fn foreign_from((response, http_code): (NmiCompleteResponse, u16)) -> Self { + Self { + code: response.response_code, + message: response.responsetext.to_owned(), + reason: Some(response.responsetext), + status_code: http_code, + attempt_status: None, + connector_transaction_id: Some(response.transactionid), + } + } +} + #[derive(Debug, Serialize)] pub struct NmiPaymentsRequest { #[serde(rename = "type")] @@ -139,7 +462,7 @@ impl TryFrom<&api_models::payments::PaymentMethodData> for PaymentMethod { payment_method_data: &api_models::payments::PaymentMethodData, ) -> Result { match &payment_method_data { - api::PaymentMethodData::Card(ref card) => Ok(Self::from(card)), + api::PaymentMethodData::Card(ref card) => Ok(Self::try_from(card)?), api::PaymentMethodData::Wallet(ref wallet_type) => match wallet_type { api_models::payments::WalletData::GooglePay(ref googlepay_data) => { Ok(Self::from(googlepay_data)) @@ -198,18 +521,19 @@ impl TryFrom<&api_models::payments::PaymentMethodData> for PaymentMethod { } } -impl From<&api_models::payments::Card> for PaymentMethod { - fn from(card: &api_models::payments::Card) -> Self { +impl TryFrom<&api_models::payments::Card> for PaymentMethod { + type Error = Error; + fn try_from(card: &api_models::payments::Card) -> Result { let ccexp = utils::CardData::get_card_expiry_month_year_2_digit_with_delimiter( card, "".to_string(), - ); + )?; let card = CardData { ccnumber: card.card_number.clone(), ccexp, cvv: card.card_cvc.clone(), }; - Self::Card(Box::new(card)) + Ok(Self::Card(Box::new(card))) } } @@ -249,7 +573,7 @@ impl TryFrom<&types::SetupMandateRouterData> for NmiPaymentsRequest { #[derive(Debug, Serialize)] pub struct NmiSyncRequest { - pub transaction_id: String, + pub order_id: String, pub security_key: Secret, } @@ -259,11 +583,7 @@ impl TryFrom<&types::PaymentsSyncRouterData> for NmiSyncRequest { let auth = NmiAuthType::try_from(&item.connector_auth_type)?; Ok(Self { security_key: auth.api_key, - transaction_id: item - .request - .connector_transaction_id - .get_connector_transaction_id() - .change_context(errors::ConnectorError::MissingConnectorTransactionID)?, + order_id: item.attempt_id.clone(), }) } } @@ -315,13 +635,13 @@ impl Response::Approved => ( Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transactionid, + item.response.transactionid.to_owned(), ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, }), enums::AttemptStatus::CaptureInitiated, @@ -409,13 +729,13 @@ impl Response::Approved => ( Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transactionid, + item.response.transactionid.to_owned(), ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, }), enums::AttemptStatus::Charged, @@ -440,11 +760,11 @@ impl ForeignFrom<(StandardResponse, u16)> for types::ErrorResponse { fn foreign_from((response, http_code): (StandardResponse, u16)) -> Self { Self { code: response.response_code, - message: response.responsetext, - reason: None, + message: response.responsetext.to_owned(), + reason: Some(response.responsetext), status_code: http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(response.transactionid), } } } @@ -465,13 +785,13 @@ impl TryFrom> Response::Approved => ( Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transactionid, + item.response.transactionid.to_owned(), ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, }), if let Some(diesel_models::enums::CaptureMethod::Automatic) = @@ -515,13 +835,13 @@ impl Response::Approved => ( Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transactionid, + item.response.transactionid.to_owned(), ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, }), enums::AttemptStatus::VoidInitiated, @@ -594,6 +914,19 @@ impl TryFrom> for SyncResponse { } } +impl TryFrom> for NmiRefundSyncResponse { + type Error = Error; + fn try_from(bytes: Vec) -> Result { + let query_response = String::from_utf8(bytes) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + query_response + .parse_xml::() + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + } +} + impl From for enums::AttemptStatus { fn from(item: NmiStatus) -> Self { match item { @@ -614,6 +947,7 @@ pub struct NmiRefundRequest { transaction_type: TransactionType, security_key: Secret, transactionid: String, + orderid: String, amount: f64, } @@ -625,6 +959,7 @@ impl TryFrom<&NmiRouterData<&types::RefundsRouterData>> for NmiRefundReque transaction_type: TransactionType::Refund, security_key: auth_type.api_key, transactionid: item.router_data.request.connector_transaction_id.clone(), + orderid: item.router_data.request.refund_id.clone(), amount: item.amount, }) } @@ -640,7 +975,7 @@ impl TryFrom> let refund_status = enums::RefundStatus::from(item.response.response); Ok(Self { response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.transactionid, + connector_refund_id: item.response.orderid, refund_status, }), ..item.data @@ -679,15 +1014,14 @@ impl TryFrom<&types::RefundSyncRouterData> for NmiSyncRequest { type Error = Error; fn try_from(item: &types::RefundSyncRouterData) -> Result { let auth = NmiAuthType::try_from(&item.connector_auth_type)?; - let transaction_id = item - .request - .connector_refund_id - .clone() - .ok_or(errors::ConnectorError::MissingConnectorRefundID)?; Ok(Self { security_key: auth.api_key, - transaction_id, + order_id: item + .request + .connector_refund_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorRefundID)?, }) } } @@ -699,12 +1033,12 @@ impl TryFrom> fn try_from( item: types::RefundsResponseRouterData, ) -> Result { - let response = SyncResponse::try_from(item.response.response.to_vec())?; + let response = NmiRefundSyncResponse::try_from(item.response.response.to_vec())?; let refund_status = enums::RefundStatus::from(NmiStatus::from(response.transaction.condition)); Ok(Self { response: Ok(types::RefundsResponseData { - connector_refund_id: response.transaction.transaction_id, + connector_refund_id: response.transaction.order_id, refund_status, }), ..item.data @@ -741,13 +1075,137 @@ impl From for NmiStatus { } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct SyncTransactionResponse { - transaction_id: String, + pub transaction_id: String, + pub condition: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SyncResponse { + pub transaction: SyncTransactionResponse, +} + +#[derive(Debug, Deserialize)] +pub struct RefundSyncBody { + order_id: String, condition: String, } #[derive(Debug, Deserialize)] -struct SyncResponse { - transaction: SyncTransactionResponse, +struct NmiRefundSyncResponse { + transaction: RefundSyncBody, +} + +#[derive(Debug, Deserialize)] +pub struct NmiWebhookObjectReference { + pub event_body: NmiReferenceBody, +} + +#[derive(Debug, Deserialize)] +pub struct NmiReferenceBody { + pub order_id: String, + pub action: NmiActionBody, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct NmiActionBody { + pub action_type: NmiActionType, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum NmiActionType { + Auth, + Capture, + Credit, + Refund, + Sale, + Void, +} + +#[derive(Debug, Deserialize)] +pub struct NmiWebhookEventBody { + pub event_type: NmiWebhookEventType, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum NmiWebhookEventType { + #[serde(rename = "transaction.sale.success")] + SaleSuccess, + #[serde(rename = "transaction.sale.failure")] + SaleFailure, + #[serde(rename = "transaction.sale.unknown")] + SaleUnknown, + #[serde(rename = "transaction.auth.success")] + AuthSuccess, + #[serde(rename = "transaction.auth.failure")] + AuthFailure, + #[serde(rename = "transaction.auth.unknown")] + AuthUnknown, + #[serde(rename = "transaction.refund.success")] + RefundSuccess, + #[serde(rename = "transaction.refund.failure")] + RefundFailure, + #[serde(rename = "transaction.refund.unknown")] + RefundUnknown, + #[serde(rename = "transaction.void.success")] + VoidSuccess, + #[serde(rename = "transaction.void.failure")] + VoidFailure, + #[serde(rename = "transaction.void.unknown")] + VoidUnknown, + #[serde(rename = "transaction.capture.success")] + CaptureSuccess, + #[serde(rename = "transaction.capture.failure")] + CaptureFailure, + #[serde(rename = "transaction.capture.unknown")] + CaptureUnknown, +} + +impl ForeignFrom for webhooks::IncomingWebhookEvent { + fn foreign_from(status: NmiWebhookEventType) -> Self { + match status { + NmiWebhookEventType::SaleSuccess => Self::PaymentIntentSuccess, + NmiWebhookEventType::SaleFailure => Self::PaymentIntentFailure, + NmiWebhookEventType::RefundSuccess => Self::RefundSuccess, + NmiWebhookEventType::RefundFailure => Self::RefundFailure, + NmiWebhookEventType::VoidSuccess => Self::PaymentIntentCancelled, + NmiWebhookEventType::AuthSuccess => Self::PaymentIntentAuthorizationSuccess, + NmiWebhookEventType::CaptureSuccess => Self::PaymentIntentCaptureSuccess, + NmiWebhookEventType::AuthFailure => Self::PaymentIntentAuthorizationFailure, + NmiWebhookEventType::CaptureFailure => Self::PaymentIntentCaptureFailure, + NmiWebhookEventType::VoidFailure => Self::PaymentIntentCancelFailure, + NmiWebhookEventType::SaleUnknown + | NmiWebhookEventType::RefundUnknown + | NmiWebhookEventType::AuthUnknown + | NmiWebhookEventType::VoidUnknown + | NmiWebhookEventType::CaptureUnknown => Self::EventNotSupported, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct NmiWebhookBody { + pub event_body: NmiWebhookObject, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct NmiWebhookObject { + pub transaction_id: String, + pub order_id: String, + pub condition: String, + pub action: NmiActionBody, +} + +impl TryFrom<&NmiWebhookBody> for SyncResponse { + type Error = Error; + fn try_from(item: &NmiWebhookBody) -> Result { + let transaction = SyncTransactionResponse { + transaction_id: item.event_body.transaction_id.to_owned(), + condition: item.event_body.condition.to_owned(), + }; + + Ok(Self { transaction }) + } } diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index 45792864255..180b4b1485f 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -3,7 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -28,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -183,6 +183,20 @@ impl types::PaymentsResponseData, > for Noon { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Noon".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -212,14 +226,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + ) -> CustomResult { + let connector_req = noon::NoonPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -237,7 +246,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + ) -> CustomResult { + let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -384,7 +388,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = noon::NoonPaymentsCancelRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -462,7 +461,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + ) -> CustomResult { + let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -540,7 +534,7 @@ impl ConnectorIntegration, + name_on_card: Option>, number_plain: cards::CardNumber, expiry_month: Secret, expiry_year: Secret, @@ -158,7 +160,7 @@ pub struct NoonPayPal { } #[derive(Debug, Serialize)] -#[serde(tag = "type", content = "data")] +#[serde(tag = "type", content = "data", rename_all = "UPPERCASE")] pub enum NoonPaymentData { Card(NoonCard), Subscription(NoonSubscription), @@ -293,7 +295,11 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { } }?, Some(item.request.currency), - item.request.order_category.clone(), + Some(item.request.order_category.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_category", + }, + )?), ), }; @@ -327,17 +333,33 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { }, }); - let (subscription, tokenize_c_c) = - match item.request.setup_future_usage.is_some().then_some(( - NoonSubscriptionData { - subscription_type: NoonSubscriptionType::Unscheduled, - name: name.clone(), - }, - true, - )) { - Some((a, b)) => (Some(a), Some(b)), - None => (None, None), - }; + let subscription = item + .request + .get_setup_mandate_details() + .map(|mandate_data| { + let max_amount = match &mandate_data.mandate_type { + Some(data_models::mandates::MandateDataType::SingleUse(mandate)) + | Some(data_models::mandates::MandateDataType::MultiUse(Some(mandate))) => { + conn_utils::to_currency_base_unit(mandate.amount, mandate.currency) + } + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "setup_future_usage.mandate_data.mandate_type", + }) + .into_report(), + }?; + + Ok::>( + NoonSubscriptionData { + subscription_type: NoonSubscriptionType::Unscheduled, + name: name.clone(), + max_amount, + }, + ) + }) + .transpose()?; + + let tokenize_c_c = subscription.is_some().then_some(true); + let order = NoonOrder { amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?, currency, diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 7a9f3af37f0..35de293443e 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -6,6 +6,7 @@ use ::common_utils::{ crypto, errors::ReportSwitchExt, ext_traits::{BytesExt, ValueExt}, + request::RequestContent, }; use error_stack::{IntoReport, ResultExt}; use transformers as nuvei; @@ -25,7 +26,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self as common_utils, ByteSliceExt}, + utils::ByteSliceExt, }; #[derive(Debug, Clone)] @@ -117,6 +118,20 @@ impl types::PaymentsResponseData, > for Nuvei { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Nuvei".to_string()) + .into(), + ) + } } impl @@ -150,16 +165,11 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let meta: nuvei::NuveiMeta = utils::to_connector_meta(req.request.connector_meta.clone())?; - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -176,7 +186,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -237,14 +247,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -257,7 +262,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentSyncRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -344,7 +344,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -429,7 +424,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -592,7 +582,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiSessionRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = nuvei::NuveiSessionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -683,7 +668,7 @@ impl .headers(types::PaymentsPreAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsPreAuthorizeType::get_request_body( + .set_body(types::PaymentsPreAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -742,15 +727,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -764,7 +744,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -846,7 +821,7 @@ impl ConnectorIntegration for PaymentOption { Self { card: Some(Card { card_number: Some(card.card_number), - card_holder_name: Some(card.card_holder_name), + card_holder_name: card.card_holder_name, expiration_month: Some(card.card_exp_month), expiration_year: Some(card.card_exp_year), three_d: card_details.three_d, diff --git a/crates/router/src/connector/opayo.rs b/crates/router/src/connector/opayo.rs index 73a793adcf7..0ab5785a445 100644 --- a/crates/router/src/connector/opayo.rs +++ b/crates/router/src/connector/opayo.rs @@ -2,6 +2,7 @@ mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -22,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -146,6 +147,20 @@ impl types::PaymentsResponseData, > for Opayo { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Opayo".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -175,14 +190,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = opayo::OpayoPaymentsRequest::try_from(req)?; - let opayo_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opayo_req)) + ) -> CustomResult { + let connector_req = opayo::OpayoPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -200,7 +210,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -337,7 +347,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = opayo::OpayoRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = opayo::OpayoRefundRequest::try_from(req)?; - let opayo_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opayo_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -421,7 +426,7 @@ impl ConnectorIntegration for OpayoPaymentsRequest { match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(req_card) => { let card = OpayoCard { - name: req_card.card_holder_name, + name: req_card + .card_holder_name + .unwrap_or(Secret::new("".to_string())), number: req_card.card_number, expiry_month: req_card.card_exp_month, expiry_year: req_card.card_exp_year, diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index c4f3d3682dc..1eafa67fdda 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::crypto; +use common_utils::{crypto, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use transformers as opennode; @@ -22,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{BytesExt, Encode}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -146,6 +146,20 @@ impl types::PaymentsResponseData, > for Opennode { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Opennode".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -175,20 +189,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = opennode::OpennodeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = opennode::OpennodePaymentsRequest::try_from(&connector_router_data)?; - let opennode_req = types::RequestBody::log_and_get_request_body( - &req_obj, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opennode_req)) + let connector_req = opennode::OpennodePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -205,7 +214,7 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { let auth = payeezy::PayeezyAuthType::try_from(&req.connector_auth_type)?; - let option_request_payload = self.get_request_body(req, connectors)?; - let request_payload = option_request_payload.map_or("{}".to_string(), |payload| { - types::RequestBody::get_inner_value(payload).expose() - }); + let request_payload = + types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).expose(); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .ok() @@ -154,6 +153,20 @@ impl types::PaymentsResponseData, > for Payeezy { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Payeezy".to_string()) + .into(), + ) + } } impl api::PaymentToken for Payeezy {} @@ -202,14 +215,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?; - let payeezy_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -222,7 +230,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = payeezy::PayeezyCaptureOrVoidRequest::try_from(&router_obj)?; - let payeezy_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(&router_obj)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -330,7 +333,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = payeezy::PayeezyPaymentsRequest::try_from(&router_obj)?; + let connector_req = payeezy::PayeezyPaymentsRequest::try_from(&router_obj)?; - let payeezy_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -430,7 +428,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = payeezy::PayeezyRefundRequest::try_from(&router_obj)?; - let payeezy_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + let connector_req = payeezy::PayeezyRefundRequest::try_from(&router_obj)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -525,7 +518,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = payme::CaptureBuyerRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payme::CaptureBuyerRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -175,7 +170,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -258,18 +253,13 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let amount = req.request.get_amount()?; let currency = req.request.get_currency()?; let connector_router_data = payme::PaymeRouterData::try_from((&self.get_currency_unit(), currency, amount, req))?; - let req_obj = payme::GenerateSaleRequest::try_from(&connector_router_data)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + let connector_req = payme::GenerateSaleRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -287,7 +277,7 @@ impl .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -339,6 +329,20 @@ impl types::PaymentsResponseData, > for Payme { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Payme".to_string()) + .into(), + ) + } } impl services::ConnectorRedirectResponse for Payme { @@ -384,14 +388,9 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::Pay3dsRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + ) -> CustomResult { + let connector_req = payme::Pay3dsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -408,7 +407,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -487,20 +486,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = payme::PaymePaymentRequest::try_from(&connector_router_data)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + let connector_req = payme::PaymePaymentRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -518,7 +512,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::PaymeQuerySaleRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + ) -> CustomResult { + let connector_req = payme::PaymeQuerySaleRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -605,7 +594,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = payme::PaymentCaptureRequest::try_from(&connector_router_data)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + let connector_req = payme::PaymentCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -701,7 +685,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = payme::PaymeRefundRequest::try_from(&connector_router_data)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + let connector_req = payme::PaymeRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -809,7 +788,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::PaymeQueryTransactionRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + ) -> CustomResult { + let connector_req = payme::PaymeQueryTransactionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -893,7 +867,7 @@ impl ConnectorIntegration } _ => { let currency_code = item.data.request.get_currency()?; + let country_code = item + .data + .address + .billing + .as_ref() + .and_then(|billing| billing.address.as_ref()) + .and_then(|address| address.country); let amount = item.data.request.get_amount()?; let amount_in_base_unit = utils::to_currency_base_unit(amount, currency_code)?; let pmd = item.data.request.payment_method_data.to_owned(); @@ -559,7 +566,7 @@ impl api_models::payments::ApplePaySessionResponse::NoSessionResponse, payment_request_data: Some( api_models::payments::ApplePayPaymentRequest { - country_code: item.data.get_billing_country()?, + country_code, currency_code, total: api_models::payments::AmountInfo { label: "Apple Pay".to_string(), @@ -641,7 +648,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest { let card = PaymeCard { credit_card_cvv: req_card.card_cvc.clone(), credit_card_exp: req_card - .get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?, credit_card_number: req_card.card_number, }; let buyer_email = item.request.get_email()?; @@ -748,7 +755,7 @@ impl TryFrom<&types::TokenizationRouterData> for CaptureBuyerRequest { let card = PaymeCard { credit_card_cvv: req_card.card_cvc.clone(), credit_card_exp: req_card - .get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?, credit_card_number: req_card.card_number, }; Ok(Self { diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index a0d39178902..742c563407c 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::{Debug, Write}; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -34,7 +34,7 @@ use crate::{ transformers::ForeignFrom, ConnectorAuthType, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -265,10 +265,6 @@ impl ConnectorValidation for Paypal { ), } } - - fn validate_if_surcharge_implemented(&self) -> CustomResult<(), errors::ConnectorError> { - Ok(()) - } } impl @@ -321,15 +317,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = paypal::PaypalAuthUpdateRequest::try_from(req)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = paypal::PaypalAuthUpdateRequest::try_from(req)?; - Ok(Some(paypal_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -342,7 +333,7 @@ impl ConnectorIntegration for Paypal { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Paypal".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -424,23 +429,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, - req.request - .surcharge_details - .as_ref() - .map_or(req.request.amount, |surcharge| surcharge.final_amount), + req.request.amount, req, ))?; - let req_obj = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -457,7 +454,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -919,12 +913,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -939,7 +928,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = paypal::PaypalRefundRequest::try_from(&connector_router_data)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + let connector_req = paypal::PaypalRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1102,7 +1086,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = paypal::PaypalSourceVerificationRequest::try_from(&req.request)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + ) -> CustomResult { + let connector_req = paypal::PaypalSourceVerificationRequest::try_from(&req.request)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 8b6a2297d09..d384bc4cfd5 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -290,7 +290,12 @@ fn get_payment_source( bank_name: _, country, } => Ok(PaymentSourceItem::Eps(RedirectRequest { - name: billing_details.get_billing_name()?, + name: billing_details + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "eps.billing_details", + })? + .get_billing_name()?, country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { field_name: "eps.country", })?, @@ -330,7 +335,12 @@ fn get_payment_source( bank_name: _, country, } => Ok(PaymentSourceItem::IDeal(RedirectRequest { - name: billing_details.get_billing_name()?, + name: billing_details + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "ideal.billing_details", + })? + .get_billing_name()?, country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { field_name: "ideal.country", })?, @@ -443,7 +453,10 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP let payment_source = Some(PaymentSourceItem::Card(CardRequest { billing_address: get_address_info(item.router_data.address.billing.as_ref())?, expiry, - name: ccard.card_holder_name.clone(), + name: ccard + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), number: Some(ccard.card_number.clone()), security_code: Some(ccard.card_cvc.clone()), attributes, @@ -483,7 +496,12 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP experience_context: ContextStruct { return_url: item.router_data.request.complete_authorize_url.clone(), cancel_url: item.router_data.request.complete_authorize_url.clone(), - shipping_preference: ShippingPreference::SetProvidedAddress, + shipping_preference: if item.router_data.address.shipping.is_some() + { + ShippingPreference::SetProvidedAddress + } else { + ShippingPreference::GetFromFile + }, user_action: Some(UserAction::PayNow), }, })); diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index 8ac3b63f72a..c44b4d5d6ff 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -22,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -127,6 +128,20 @@ impl types::PaymentsResponseData, > for Payu { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Payu".to_string()) + .into(), + ) + } } impl api::PaymentToken for Payu {} @@ -246,15 +261,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = payu::PayuAuthUpdateRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = payu::PayuAuthUpdateRequest::try_from(req)?; - Ok(Some(payu_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -268,7 +278,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuPaymentsCaptureRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -447,7 +452,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuPaymentsRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -550,7 +550,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuRefundRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -640,7 +635,7 @@ impl ConnectorIntegration for Placetopay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Placetopay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Placetopay { + fn id(&self) -> &'static str { + "placetopay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.placetopay.base_url.as_ref() + } + + fn build_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayErrorResponse = res + .response + .parse_struct("PlacetopayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.status.reason.to_owned(), + message: response.status.message.to_owned(), + reason: Some(response.status.message), + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Placetopay { + fn validate_capture_method( + &self, + capture_method: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled => Err(utils::construct_not_supported_error_report( + capture_method, + self.id(), + )), + } + } +} + +impl ConnectorIntegration + for Placetopay +{ +} + +impl ConnectorIntegration + for Placetopay +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Placetopay +{ + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Placetopay".to_string()) + .into(), + ) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/process", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = placetopay::PlacetopayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = placetopay::PlacetopayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayPaymentsResponse = res + .response + .parse_struct("Placetopay PlacetopayPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/query", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = placetopay::PlacetopayPsyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayPaymentsResponse = res + .response + .parse_struct("placetopay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/transaction", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = placetopay::PlacetopayNextActionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayPaymentsResponse = res + .response + .parse_struct("Placetopay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/transaction", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsCancelRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = placetopay::PlacetopayNextActionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCancelRouterData, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayPaymentsResponse = res + .response + .parse_struct("Placetopay PaymentCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/transaction", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = placetopay::PlacetopayRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: placetopay::PlacetopayRefundResponse = res + .response + .parse_struct("placetopay PlacetopayRefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Placetopay +{ + fn get_headers( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/query", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = placetopay::PlacetopayRsyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundSyncRouterData, + res: Response, + ) -> CustomResult { + let response: placetopay::PlacetopayRefundResponse = res + .response + .parse_struct("placetopay PlacetopayRefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Placetopay { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/placetopay/transformers.rs b/crates/router/src/connector/placetopay/transformers.rs new file mode 100644 index 00000000000..dfdd3f904df --- /dev/null +++ b/crates/router/src/connector/placetopay/transformers.rs @@ -0,0 +1,493 @@ +use api_models::payments; +use common_utils::date_time; +use diesel_models::enums; +use error_stack::{IntoReport, ResultExt}; +use masking::{PeekInterface, Secret}; +use ring::digest; +use serde::{Deserialize, Serialize}; + +use crate::{ + connector::utils::{ + self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, + PaymentsSyncRequestData, RouterData, + }, + consts, + core::errors, + types::{self, api, storage::enums as storage_enums}, +}; + +pub struct PlacetopayRouterData { + pub amount: i64, + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for PlacetopayRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + Ok(Self { + amount, + router_data: item, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayPaymentsRequest { + auth: PlacetopayAuth, + payment: PlacetopayPayment, + instrument: PlacetopayInstrument, + ip_address: Secret, + user_agent: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum PlacetopayAuthorizeAction { + Checkin, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayAuthType { + login: Secret, + tran_key: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayAuth { + login: Secret, + tran_key: Secret, + nonce: String, + seed: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayPayment { + reference: String, + description: String, + amount: PlacetopayAmount, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayAmount { + currency: storage_enums::Currency, + total: i64, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayInstrument { + card: PlacetopayCard, +} + +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayCard { + number: cards::CardNumber, + expiration: Secret, + cvv: Secret, +} + +impl TryFrom<&PlacetopayRouterData<&types::PaymentsAuthorizeRouterData>> + for PlacetopayPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &PlacetopayRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let browser_info = item.router_data.request.get_browser_info()?; + let ip_address = browser_info.get_ip_address()?; + let user_agent = browser_info.get_user_agent()?; + let auth = PlacetopayAuth::try_from(&item.router_data.connector_auth_type)?; + let payment = PlacetopayPayment { + reference: item.router_data.connector_request_reference_id.clone(), + description: item.router_data.get_description()?, + amount: PlacetopayAmount { + currency: item.router_data.request.currency, + total: item.amount, + }, + }; + match item.router_data.request.payment_method_data.clone() { + payments::PaymentMethodData::Card(req_card) => { + let card = PlacetopayCard { + number: req_card.card_number.clone(), + expiration: req_card + .clone() + .get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?, + cvv: req_card.card_cvc.clone(), + }; + Ok(Self { + ip_address, + user_agent, + auth, + payment, + instrument: PlacetopayInstrument { + card: card.to_owned(), + }, + }) + } + payments::PaymentMethodData::Wallet(_) + | payments::PaymentMethodData::CardRedirect(_) + | payments::PaymentMethodData::PayLater(_) + | payments::PaymentMethodData::BankRedirect(_) + | payments::PaymentMethodData::BankDebit(_) + | payments::PaymentMethodData::BankTransfer(_) + | payments::PaymentMethodData::Crypto(_) + | payments::PaymentMethodData::MandatePayment + | payments::PaymentMethodData::Reward + | payments::PaymentMethodData::Upi(_) + | payments::PaymentMethodData::Voucher(_) + | payments::PaymentMethodData::GiftCard(_) + | payments::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Placetopay"), + ) + .into()) + } + } + } +} + +impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuth { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + let placetopay_auth = PlacetopayAuthType::try_from(auth_type)?; + let nonce_bytes = utils::generate_random_bytes(16); + let now = error_stack::IntoReport::into_report(date_time::date_as_yyyymmddthhmmssmmmz()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let seed = format!("{}+00:00", now.split_at(now.len() - 5).0); + let mut context = digest::Context::new(&digest::SHA256); + context.update(&nonce_bytes); + context.update(seed.as_bytes()); + context.update(placetopay_auth.tran_key.peek().as_bytes()); + let encoded_digest = base64::Engine::encode(&consts::BASE64_ENGINE, context.finish()); + let nonce = base64::Engine::encode(&consts::BASE64_ENGINE, &nonce_bytes); + Ok(Self { + login: placetopay_auth.login, + tran_key: encoded_digest.into(), + nonce, + seed, + }) + } +} + +impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuthType { + type Error = error_stack::Report; + + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + Ok(Self { + login: api_key.to_owned(), + tran_key: key1.to_owned(), + }) + } else { + Err(errors::ConnectorError::FailedToObtainAuthType)? + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PlacetopayStatus { + Ok, + Failed, + Approved, + Rejected, + Pending, + PendingValidation, + PendingProcess, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayStatusResponse { + status: PlacetopayStatus, +} + +impl From for enums::AttemptStatus { + fn from(item: PlacetopayStatus) -> Self { + match item { + PlacetopayStatus::Approved | PlacetopayStatus::Ok => Self::Authorized, + PlacetopayStatus::Failed | PlacetopayStatus::Rejected => Self::Failure, + PlacetopayStatus::Pending + | PlacetopayStatus::PendingValidation + | PlacetopayStatus::PendingProcess => Self::Authorizing, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayPaymentsResponse { + status: PlacetopayStatusResponse, + internal_reference: u64, +} + +impl + TryFrom< + types::ResponseRouterData, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + PlacetopayPaymentsResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: enums::AttemptStatus::from(item.response.status.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.internal_reference.to_string(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } +} + +// REFUND : +// Type definition for RefundRequest +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayRefundRequest { + auth: PlacetopayAuth, + internal_reference: u64, + action: PlacetopayNextAction, +} + +impl TryFrom<&types::RefundsRouterData> for PlacetopayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefundsRouterData) -> Result { + let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; + let internal_reference = item + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let action = PlacetopayNextAction::Refund; + + Ok(Self { + auth, + internal_reference, + action, + }) + } +} + +impl From for enums::RefundStatus { + fn from(item: PlacetopayRefundStatus) -> Self { + match item { + PlacetopayRefundStatus::Refunded => Self::Success, + PlacetopayRefundStatus::Failed | PlacetopayRefundStatus::Rejected => Self::Failure, + PlacetopayRefundStatus::Pending | PlacetopayRefundStatus::PendingProcess => { + Self::Pending + } + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayRefundResponse { + status: PlacetopayRefundStatus, + internal_reference: u64, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PlacetopayRefundStatus { + Refunded, + Rejected, + Failed, + Pending, + PendingProcess, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.internal_reference.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayRsyncRequest { + auth: PlacetopayAuth, + internal_reference: u64, +} + +impl TryFrom<&types::RefundsRouterData> for PlacetopayRsyncRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefundsRouterData) -> Result { + let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; + let internal_reference = item + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Self { + auth, + internal_reference, + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.internal_reference.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayErrorResponse { + pub status: PlacetopayError, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayError { + pub status: PlacetopayErrorStatus, + pub message: String, + pub reason: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PlacetopayErrorStatus { + Failed, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayPsyncRequest { + auth: PlacetopayAuth, + internal_reference: u64, +} + +impl TryFrom<&types::PaymentsSyncRouterData> for PlacetopayPsyncRequest { + type Error = error_stack::Report; + + fn try_from(item: &types::PaymentsSyncRouterData) -> Result { + let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; + let internal_reference = item + .request + .get_connector_transaction_id()? + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Self { + auth, + internal_reference, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PlacetopayNextActionRequest { + auth: PlacetopayAuth, + internal_reference: u64, + action: PlacetopayNextAction, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum PlacetopayNextAction { + Refund, + Void, + Process, + Checkout, +} + +impl TryFrom<&types::PaymentsCaptureRouterData> for PlacetopayNextActionRequest { + type Error = error_stack::Report; + + fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; + let internal_reference = item + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let action = PlacetopayNextAction::Checkout; + Ok(Self { + auth, + internal_reference, + action, + }) + } +} + +impl TryFrom<&types::PaymentsCancelRouterData> for PlacetopayNextActionRequest { + type Error = error_stack::Report; + + fn try_from(item: &types::PaymentsCancelRouterData) -> Result { + let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; + let internal_reference = item + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let action = PlacetopayNextAction::Void; + Ok(Self { + auth, + internal_reference, + action, + }) + } +} diff --git a/crates/router/src/connector/powertranz.rs b/crates/router/src/connector/powertranz.rs index 8cbf80dbaf5..654128bb3fd 100644 --- a/crates/router/src/connector/powertranz.rs +++ b/crates/router/src/connector/powertranz.rs @@ -3,7 +3,7 @@ pub mod transformers; use std::fmt::Debug; use api_models::enums::AuthenticationType; -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -27,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -158,6 +158,20 @@ impl types::PaymentsResponseData, > for Powertranz { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Powertranz".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -196,14 +210,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzPaymentsRequest::try_from(req)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + ) -> CustomResult { + let connector_req = powertranz::PowertranzPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -221,7 +230,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let redirect_payload: powertranz::RedirectResponsePayload = req .request .get_redirect_response_payload()? .parse_value("PowerTranz RedirectResponsePayload") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; let spi_token = format!(r#""{}""#, redirect_payload.spi_token); - let powertranz_req = - types::RequestBody::log_and_get_request_body(&spi_token, |spi_token| { - Ok(spi_token.to_string()) - }) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(spi_token))) } fn build_request( @@ -313,7 +317,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -377,14 +381,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(&req.request)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -400,7 +399,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(&req.request)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -491,7 +485,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(req)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -555,7 +544,7 @@ impl ConnectorIntegration for PowertranzPaymentsRequest type Error = error_stack::Report; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { let source = match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(card) => Ok(Source::from(&card)), + api::PaymentMethodData::Card(card) => Source::try_from(&card), api::PaymentMethodData::Wallet(_) | api::PaymentMethodData::CardRedirect(_) | api::PaymentMethodData::PayLater(_) @@ -211,15 +211,19 @@ impl TryFrom<&types::BrowserInformation> for BrowserInfo { }) }*/ -impl From<&Card> for Source { - fn from(card: &Card) -> Self { +impl TryFrom<&Card> for Source { + type Error = error_stack::Report; + fn try_from(card: &Card) -> Result { let card = PowertranzCard { - cardholder_name: card.card_holder_name.clone(), + cardholder_name: card + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), card_pan: card.card_number.clone(), - card_expiration: card.get_expiry_date_as_yymm(), + card_expiration: card.get_expiry_date_as_yymm()?, card_cvv: card.card_cvc.clone(), }; - Self::Card(card) + Ok(Self::Card(card)) } } diff --git a/crates/router/src/connector/prophetpay.rs b/crates/router/src/connector/prophetpay.rs index 4a1217a8c5b..fc14f37a2b2 100644 --- a/crates/router/src/connector/prophetpay.rs +++ b/crates/router/src/connector/prophetpay.rs @@ -3,6 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as prophetpay; @@ -22,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -144,6 +145,20 @@ impl types::PaymentsResponseData, > for Prophetpay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Prophetpay".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -176,21 +191,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = prophetpay::ProphetpayTokenRequest::try_from(&connector_router_data)?; + let connector_req = prophetpay::ProphetpayTokenRequest::try_from(&connector_router_data)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -208,7 +218,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = prophetpay::ProphetpayCompleteRequest::try_from(&connector_router_data)?; + let connector_req = + prophetpay::ProphetpayCompleteRequest::try_from(&connector_router_data)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -309,7 +315,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -373,15 +379,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpaySyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = prophetpay::ProphetpaySyncRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -395,7 +396,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpayVoidRequest::try_from(req)?; + ) -> CustomResult { + let connector_req =prophetpay::ProphetpayVoidRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } */ @@ -540,20 +536,15 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = prophetpay::ProphetpayRefundRequest::try_from(&connector_router_data)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + let connector_req = prophetpay::ProphetpayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -568,7 +559,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -652,7 +638,7 @@ impl ConnectorIntegration>(); for value in values { let pair = value.split('=').collect::>(); - queries.insert(pair[0].to_string(), pair[1].to_string()); + queries.insert( + pair.first() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + pair.get(1) + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + ); } - queries + Ok(queries) }) + .transpose() + .into_report()? .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; for (key, val) in queries_params { @@ -307,8 +316,8 @@ fn get_card_token( Err(errors::ConnectorError::MissingRequiredField { field_name: "card_token", - }) - .into_report() + } + .into()) } #[derive(Debug, Clone, Serialize)] diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index 54285e1cfbb..5272a8ab21e 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::{date_time, ext_traits::StringExt}; +use common_utils::{date_time, ext_traits::StringExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -10,9 +10,9 @@ use rand::distributions::{Alphanumeric, DistString}; use ring::hmac; use transformers as rapyd; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::{utils as connector_utils, utils as conn_utils}, consts, core::errors::{self, CustomResult}, headers, logger, @@ -185,20 +185,15 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; - let rapyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(rapyd_req)) + let connector_req = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -214,8 +209,7 @@ impl let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?; - let body = types::PaymentsAuthorizeType::get_request_body(self, req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let body = types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?; let req_body = types::RequestBody::get_inner_value(body).expose(); let signature = self.generate_signature(&auth, "post", "/v1/payments", &req_body, ×tamp, &salt)?; @@ -235,7 +229,7 @@ impl self, req, connectors, )?) .headers(headers) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(); @@ -278,6 +272,20 @@ impl types::PaymentsResponseData, > for Rapyd { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Rapyd".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Rapyd {} @@ -498,20 +506,15 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = rapyd::CaptureRequest::try_from(&connector_router_data)?; - let rapyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(rapyd_req)) + let connector_req = rapyd::CaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -527,8 +530,7 @@ impl "/v1/payments/{}/capture", req.request.connector_transaction_id ); - let body = types::PaymentsCaptureType::get_request_body(self, req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let body = types::PaymentsCaptureType::get_request_body(self, req, connectors)?; let req_body = types::RequestBody::get_inner_value(body).expose(); let signature = self.generate_signature(&auth, "post", &url_path, &req_body, ×tamp, &salt)?; @@ -546,7 +548,7 @@ impl self, req, connectors, )?) .headers(headers) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(); @@ -639,21 +641,16 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; - let rapyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let connector_req = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; - Ok(Some(rapyd_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -664,8 +661,7 @@ impl services::ConnectorIntegration, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = conn_utils::get_header_key_value("signature", request.headers)?; + let base64_signature = connector_utils::get_header_key_value("signature", request.headers)?; let signature = consts::BASE64_ENGINE_URL_SAFE .decode(base64_signature.as_bytes()) .into_report() @@ -765,11 +761,11 @@ impl api::IncomingWebhook for Rapyd { merchant_id: &str, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let host = conn_utils::get_header_key_value("host", request.headers)?; + let host = connector_utils::get_header_key_value("host", request.headers)?; let connector = self.id(); let url_path = format!("https://{host}/webhooks/{merchant_id}/{connector}"); - let salt = conn_utils::get_header_key_value("salt", request.headers)?; - let timestamp = conn_utils::get_header_key_value("timestamp", request.headers)?; + let salt = connector_utils::get_header_key_value("salt", request.headers)?; + let timestamp = connector_utils::get_header_key_value("timestamp", request.headers)?; let stringify_auth = String::from_utf8(connector_webhook_secrets.secret.to_vec()) .into_report() .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs index 193eb819892..9fd664748e3 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/router/src/connector/rapyd/transformers.rs @@ -131,7 +131,10 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay number: ccard.card_number.to_owned(), expiration_month: ccard.card_exp_month.to_owned(), expiration_year: ccard.card_exp_year.to_owned(), - name: ccard.card_holder_name.to_owned(), + name: ccard + .card_holder_name + .to_owned() + .unwrap_or(Secret::new("".to_string())), cvv: ccard.card_cvc.to_owned(), }), address: None, diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs new file mode 100644 index 00000000000..29d57ee28cd --- /dev/null +++ b/crates/router/src/connector/riskified.rs @@ -0,0 +1,550 @@ +pub mod transformers; +use std::fmt::Debug; + +#[cfg(feature = "frm")] +use common_utils::request::RequestContent; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, PeekInterface}; +use ring::hmac; +use transformers as riskified; + +#[cfg(feature = "frm")] +use super::utils::FrmTransactionRouterDataRequest; +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + services::{self, request, ConnectorIntegration, ConnectorValidation}, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + }, +}; +#[cfg(feature = "frm")] +use crate::{ + types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + utils::BytesExt, +}; + +#[derive(Debug, Clone)] +pub struct Riskified; + +impl Riskified { + pub fn generate_authorization_signature( + &self, + auth: &riskified::RiskifiedAuthType, + payload: &str, + ) -> CustomResult { + let key = hmac::Key::new( + hmac::HMAC_SHA256, + auth.secret_token.clone().expose().as_bytes(), + ); + + let signature_value = hmac::sign(&key, payload.as_bytes()); + + let digest = signature_value.as_ref(); + + Ok(hex::encode(digest)) + } +} + +impl ConnectorCommonExt for Riskified +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let auth: riskified::RiskifiedAuthType = + riskified::RiskifiedAuthType::try_from(&req.connector_auth_type)?; + + let riskified_req = self.get_request_body(req, connectors)?; + + let binding = types::RequestBody::get_inner_value(riskified_req); + let payload = binding.peek(); + + let digest = self + .generate_authorization_signature(&auth, payload) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + "X-RISKIFIED-SHOP-DOMAIN".to_string(), + auth.domain_name.clone().into(), + ), + ( + "X-RISKIFIED-HMAC-SHA256".to_string(), + request::Mask::into_masked(digest), + ), + ( + "Accept".to_string(), + "application/vnd.riskified.com; version=2".into(), + ), + ]; + + Ok(header) + } +} + +impl ConnectorCommon for Riskified { + fn id(&self) -> &'static str { + "riskified" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.riskified.base_url.as_ref() + } + + #[cfg(feature = "frm")] + fn build_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: riskified::ErrorResponse = res + .response + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(ErrorResponse { + status_code: res.status_code, + attempt_status: None, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: response.error.message.clone(), + reason: None, + connector_transaction_id: None, + }) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Checkout, + frm_types::FraudCheckCheckoutData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "/decide")) + } + + fn get_request_body( + &self, + req: &frm_types::FrmCheckoutRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmCheckoutType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(frm_types::FrmCheckoutType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmCheckoutType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmCheckoutRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedPaymentsResponse = res + .response + .parse_struct("RiskifiedPaymentsResponse Checkout") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl api::Payment for Riskified {} +impl api::PaymentAuthorize for Riskified {} +impl api::PaymentSync for Riskified {} +impl api::PaymentVoid for Riskified {} +impl api::PaymentCapture for Riskified {} +impl api::MandateSetup for Riskified {} +impl api::ConnectorAccessToken for Riskified {} +impl api::PaymentToken for Riskified {} +impl api::Refund for Riskified {} +impl api::RefundExecute for Riskified {} +impl api::RefundSync for Riskified {} +impl ConnectorValidation for Riskified {} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Sale, + frm_types::FraudCheckSaleData, + frm_types::FraudCheckResponseData, + > for Riskified +{ +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Transaction, + frm_types::FraudCheckTransactionData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + match req.is_payment_successful() { + Some(false) => Ok(format!( + "{}{}", + self.base_url(connectors), + "/checkout_denied" + )), + Some(true) => Ok(format!("{}{}", self.base_url(connectors), "/decision")), + None => Err(errors::ConnectorError::FlowNotSupported { + flow: "Transaction".to_owned(), + connector: req.connector.to_string(), + })?, + } + } + + fn get_request_body( + &self, + req: &frm_types::FrmTransactionRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + match req.is_payment_successful() { + Some(false) => { + let req_obj = riskified::TransactionFailedRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + Some(true) => { + let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + None => Err(errors::ConnectorError::FlowNotSupported { + flow: "Transaction".to_owned(), + connector: req.connector.to_owned(), + })?, + } + } + + fn build_request( + &self, + req: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmTransactionType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmTransactionType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmTransactionType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmTransactionRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedTransactionResponse = res + .response + .parse_struct("RiskifiedPaymentsResponse Transaction") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + match response { + riskified::RiskifiedTransactionResponse::FailedResponse(response_data) => { + ::try_from(types::ResponseRouterData { + response: response_data, + data: data.clone(), + http_code: res.status_code, + }) + } + riskified::RiskifiedTransactionResponse::SuccessResponse(response_data) => { + ::try_from(types::ResponseRouterData { + response: response_data, + data: data.clone(), + http_code: res.status_code, + }) + } + } + } + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "/fulfill")) + } + + fn get_request_body( + &self, + req: &frm_types::FrmFulfillmentRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = riskified::RiskifiedFullfillmentRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmFulfillmentType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmFulfillmentType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmFulfillmentType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmFulfillmentRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedFulfilmentResponse = res + .response + .parse_struct("RiskifiedFulfilmentResponse fulfilment") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + frm_types::FrmFulfillmentRouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::RecordReturn, + frm_types::FraudCheckRecordReturnData, + frm_types::FraudCheckResponseData, + > for Riskified +{ +} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Riskified +{ + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Riskified".to_string()) + .into(), + ) + } +} + +impl api::PaymentSession for Riskified {} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +#[cfg(feature = "frm")] +impl api::FraudCheck for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckSale for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckCheckout for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckTransaction for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckFulfillment for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckRecordReturn for Riskified {} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Riskified { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/riskified/transformers.rs b/crates/router/src/connector/riskified/transformers.rs new file mode 100644 index 00000000000..4f155f341f6 --- /dev/null +++ b/crates/router/src/connector/riskified/transformers.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "frm")] +pub mod api; +pub mod auth; + +#[cfg(feature = "frm")] +pub use self::api::*; +pub use self::auth::*; diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs new file mode 100644 index 00000000000..de8884f0390 --- /dev/null +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -0,0 +1,597 @@ +use api_models::payments::AdditionalPaymentData; +use common_utils::{ext_traits::ValueExt, pii::Email}; +use error_stack::{self, ResultExt}; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{ + connector::utils::{ + AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckTransactionRequest, RouterData, + }, + core::{errors, fraud_check::types as core_types}, + types::{ + self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + ResponseId, ResponseRouterData, + }, +}; + +type Error = error_stack::Report; + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedPaymentsCheckoutRequest { + order: CheckoutRequest, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct CheckoutRequest { + id: String, + note: Option, + email: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + currency: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + updated_at: PrimitiveDateTime, + gateway: Option, + browser_ip: Option, + total_price: i64, + total_discounts: i64, + cart_token: String, + referring_site: String, + line_items: Vec, + discount_codes: Vec, + shipping_lines: Vec, + payment_details: Option, + customer: RiskifiedCustomer, + billing_address: Option, + shipping_address: Option, + source: Source, + client_details: ClientDetails, + vendor_name: String, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct PaymentDetails { + credit_card_bin: Option>, + credit_card_number: Option>, + credit_card_company: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ShippingLines { + price: i64, + title: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct DiscountCodes { + amount: i64, + code: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ClientDetails { + user_agent: Option, + accept_language: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedCustomer { + email: Option, + first_name: Option>, + last_name: Option>, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + verified_email: bool, + id: String, + account_type: CustomerAccountType, + orders_count: i32, + phone: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum CustomerAccountType { + Guest, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderAddress { + first_name: Option>, + last_name: Option>, + address1: Option>, + country_code: Option, + city: Option, + province: Option>, + phone: Option>, + zip: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct LineItem { + price: i64, + quantity: i32, + title: String, + product_type: Option, + requires_shipping: Option, + product_id: Option, + category: Option, + brand: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +pub enum Source { + DesktopWeb, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedMetadata { + vendor_name: String, + shipping_lines: Vec, +} + +impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutRequest { + type Error = Error; + fn try_from(payment_data: &frm_types::FrmCheckoutRouterData) -> Result { + let metadata: RiskifiedMetadata = payment_data + .frm_metadata + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "frm_metadata", + })? + .parse_value("Riskified Metadata") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let billing_address = payment_data.get_billing_address_with_phone_number()?; + let shipping_address = payment_data.get_shipping_address_with_phone_number()?; + let address = payment_data.get_billing_address()?; + + Ok(Self { + order: CheckoutRequest { + id: payment_data.attempt_id.clone(), + email: payment_data.request.email.clone(), + created_at: common_utils::date_time::now(), + updated_at: common_utils::date_time::now(), + gateway: payment_data.request.gateway.clone(), + total_price: payment_data.request.amount, + cart_token: payment_data.attempt_id.clone(), + line_items: payment_data + .request + .get_order_details()? + .iter() + .map(|order_detail| LineItem { + price: order_detail.amount, + quantity: i32::from(order_detail.quantity), + title: order_detail.product_name.clone(), + product_type: order_detail.product_type.clone(), + requires_shipping: order_detail.requires_shipping, + product_id: order_detail.product_id.clone(), + category: order_detail.category.clone(), + brand: order_detail.brand.clone(), + }) + .collect::>(), + source: Source::DesktopWeb, + billing_address: OrderAddress::try_from(billing_address).ok(), + shipping_address: OrderAddress::try_from(shipping_address).ok(), + total_discounts: 0, + currency: payment_data.request.currency, + referring_site: "hyperswitch.io".to_owned(), + discount_codes: Vec::new(), + shipping_lines: metadata.shipping_lines, + customer: RiskifiedCustomer { + email: payment_data.request.email.clone(), + + first_name: address.get_first_name().ok().cloned(), + last_name: address.get_last_name().ok().cloned(), + created_at: common_utils::date_time::now(), + verified_email: false, + id: payment_data.get_customer_id()?, + account_type: CustomerAccountType::Guest, + orders_count: 0, + phone: billing_address + .clone() + .phone + .and_then(|phone_data| phone_data.number), + }, + browser_ip: payment_data + .request + .browser_info + .as_ref() + .and_then(|browser_info| browser_info.ip_address), + client_details: ClientDetails { + user_agent: payment_data + .request + .browser_info + .as_ref() + .and_then(|browser_info| browser_info.user_agent.clone()), + accept_language: payment_data.request.browser_info.as_ref().and_then( + |browser_info: &types::BrowserInformation| browser_info.language.clone(), + ), + }, + note: payment_data.description.clone(), + vendor_name: metadata.vendor_name, + payment_details: match payment_data.request.payment_method_data.as_ref() { + Some(AdditionalPaymentData::Card(card_info)) => Some(PaymentDetails { + credit_card_bin: card_info.card_isin.clone().map(Secret::new), + credit_card_number: card_info + .last4 + .clone() + .map(|last_four| format!("XXXX-XXXX-XXXX-{}", last_four)) + .map(Secret::new), + credit_card_company: card_info.card_network.clone(), + }), + Some(_) | None => None, + }, + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedPaymentsResponse { + order: OrderResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderResponse { + id: String, + status: PaymentStatus, + description: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFulfilmentResponse { + order: OrderFulfilmentResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderFulfilmentResponse { + id: String, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum FulfilmentStatus { + Fulfilled, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PaymentStatus { + Captured, + Created, + Submitted, + Approved, + Declined, + Processing, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + F, + RiskifiedPaymentsResponse, + T, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order.id), + status: storage_enums::FraudCheckStatus::from(item.response.order.status), + connector_metadata: None, + score: None, + reason: item.response.order.description.map(serde_json::Value::from), + }), + ..item.data + }) + } +} + +impl From for storage_enums::FraudCheckStatus { + fn from(item: PaymentStatus) -> Self { + match item { + PaymentStatus::Approved => Self::Legit, + PaymentStatus::Declined => Self::Fraud, + _ => Self::Pending, + } + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionFailedRequest { + checkout: FailedTransactionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct FailedTransactionData { + id: String, + payment_details: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct DeclinedPaymentDetails { + authorization_error: AuthorizationError, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct AuthorizationError { + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + error_code: Option, + message: Option, +} + +impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionFailedRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + Ok(Self { + checkout: FailedTransactionData { + id: item.attempt_id.clone(), + payment_details: [DeclinedPaymentDetails { + authorization_error: AuthorizationError { + created_at: common_utils::date_time::now(), + error_code: item.request.error_code.clone(), + message: item.request.error_message.clone(), + }, + }] + .to_vec(), + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFailedTransactionResponse { + checkout: OrderResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(untagged)] +pub enum RiskifiedTransactionResponse { + FailedResponse(RiskifiedFailedTransactionResponse), + SuccessResponse(RiskifiedPaymentsResponse), +} + +impl + TryFrom< + ResponseRouterData< + F, + RiskifiedFailedTransactionResponse, + T, + frm_types::FraudCheckResponseData, + >, + > for types::RouterData +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + F, + RiskifiedFailedTransactionResponse, + T, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.checkout.id), + status: storage_enums::FraudCheckStatus::from(item.response.checkout.status), + connector_metadata: None, + score: None, + reason: item + .response + .checkout + .description + .map(serde_json::Value::from), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionSuccessRequest { + order: SuccessfulTransactionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct SuccessfulTransactionData { + id: String, + decision: TransactionDecisionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionDecisionData { + external_status: TransactionStatus, + reason: Option, + amount: i64, + currency: storage_enums::Currency, + #[serde(with = "common_utils::custom_serde::iso8601")] + decided_at: PrimitiveDateTime, + payment_details: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionPaymentDetails { + authorization_id: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum TransactionStatus { + Approved, +} + +impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionSuccessRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + Ok(Self { + order: SuccessfulTransactionData { + id: item.attempt_id.clone(), + decision: TransactionDecisionData { + external_status: TransactionStatus::Approved, + reason: None, + amount: item.request.amount, + currency: item.request.get_currency()?, + decided_at: common_utils::date_time::now(), + payment_details: [TransactionPaymentDetails { + authorization_id: item.request.connector_transaction_id.clone(), + }] + .to_vec(), + }, + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFullfillmentRequest { + order: OrderFullfillment, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum FulfillmentRequestStatus { + Success, + Cancelled, + Error, + Failure, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderFullfillment { + id: String, + fulfillments: FulfilmentData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct FulfilmentData { + fulfillment_id: String, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + status: Option, + tracking_company: String, + tracking_number: String, + tracking_url: Option, +} + +impl TryFrom<&frm_types::FrmFulfillmentRouterData> for RiskifiedFullfillmentRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmFulfillmentRouterData) -> Result { + Ok(Self { + order: OrderFullfillment { + id: item.attempt_id.clone(), + fulfillments: FulfilmentData { + fulfillment_id: item.payment_id.clone(), + created_at: common_utils::date_time::now(), + status: item + .request + .fulfillment_req + .fulfillment_status + .clone() + .and_then(get_fulfillment_status), + tracking_company: item + .request + .fulfillment_req + .tracking_company + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "tracking_company", + })?, + tracking_number: item.request.fulfillment_req.tracking_number.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "tracking_number", + }, + )?, + tracking_url: item.request.fulfillment_req.tracking_url.clone(), + }, + }, + }) + } +} + +impl + TryFrom< + ResponseRouterData< + Fulfillment, + RiskifiedFulfilmentResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + > + for types::RouterData< + Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + Fulfillment, + RiskifiedFulfilmentResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::FulfillmentResponse { + order_id: item.response.order.id, + shipment_ids: Vec::new(), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ErrorResponse { + pub error: ErrorData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ErrorData { + pub message: String, +} + +impl TryFrom<&api_models::payments::Address> for OrderAddress { + type Error = Error; + fn try_from(address_info: &api_models::payments::Address) -> Result { + let address = + address_info + .clone() + .address + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "address", + })?; + Ok(Self { + first_name: address.first_name.clone(), + last_name: address.last_name.clone(), + address1: address.line1.clone(), + country_code: address.country, + city: address.city.clone(), + province: address.state.clone(), + zip: address.zip.clone(), + phone: address_info + .phone + .clone() + .and_then(|phone_data| phone_data.number), + }) + } +} + +fn get_fulfillment_status( + status: core_types::FulfillmentStatus, +) -> Option { + match status { + core_types::FulfillmentStatus::COMPLETE => Some(FulfillmentRequestStatus::Success), + core_types::FulfillmentStatus::CANCELED => Some(FulfillmentRequestStatus::Cancelled), + core_types::FulfillmentStatus::PARTIAL | core_types::FulfillmentStatus::REPLACEMENT => None, + } +} diff --git a/crates/router/src/connector/riskified/transformers/auth.rs b/crates/router/src/connector/riskified/transformers/auth.rs new file mode 100644 index 00000000000..6968bb55a59 --- /dev/null +++ b/crates/router/src/connector/riskified/transformers/auth.rs @@ -0,0 +1,22 @@ +use error_stack; +use masking::{ExposeInterface, Secret}; + +use crate::{core::errors, types}; + +pub struct RiskifiedAuthType { + pub secret_token: Secret, + pub domain_name: String, +} + +impl TryFrom<&types::ConnectorAuthType> for RiskifiedAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + secret_token: api_key.to_owned(), + domain_name: key1.to_owned().expose(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index dfb4a7de081..c38fac56efc 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as shift4; @@ -26,7 +26,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -158,6 +158,20 @@ impl types::PaymentsResponseData, > for Shift4 { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Shift4".to_string()) + .into(), + ) + } } #[async_trait::async_trait] @@ -188,14 +202,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } async fn execute_pretasks( @@ -251,7 +260,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -498,10 +502,9 @@ impl services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsInitType::get_url(self, req, connectors)?) - .content_type(request::ContentType::FormUrlEncoded) .attach_default_headers() .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) - .body(types::PaymentsInitType::get_request_body( + .set_body(types::PaymentsInitType::get_request_body( self, req, connectors, )?) .build(), @@ -564,14 +567,9 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -588,7 +586,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -645,14 +643,9 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = shift4::Shift4RefundRequest::try_from(req)?; - let shift4_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(shift4_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -667,7 +660,7 @@ impl ConnectorIntegration number: card.card_number.clone(), exp_month: card.card_exp_month.clone(), exp_year: card.card_exp_year.clone(), - cardholder_name: card.card_holder_name.clone(), + cardholder_name: card + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), }; if item.is_three_ds() { Ok(Self::Cards3DSRequest(Box::new(Cards3DSRequest { diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 5d9714e4d94..544dfd137db 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -1,6 +1,8 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "frm")] +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as signifyd; @@ -9,7 +11,7 @@ use crate::{ configs::settings, core::errors::{self, CustomResult}, headers, - services::{request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -17,9 +19,8 @@ use crate::{ }; #[cfg(feature = "frm")] use crate::{ - services, types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -125,6 +126,20 @@ impl types::PaymentsResponseData, > for Signifyd { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Signifyd".to_string()) + .into(), + ) + } } impl api::PaymentSession for Signifyd {} @@ -210,14 +225,9 @@ impl &self, req: &frm_types::FrmSaleRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = signifyd::SignifydPaymentsSaleRequest::try_from(req)?; - let signifyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(signifyd_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -231,7 +241,7 @@ impl .url(&frm_types::FrmSaleType::get_url(self, req, connectors)?) .attach_default_headers() .headers(frm_types::FrmSaleType::get_headers(self, req, connectors)?) - .body(frm_types::FrmSaleType::get_request_body( + .set_body(frm_types::FrmSaleType::get_request_body( self, req, connectors, )?) .build(), @@ -297,14 +307,9 @@ impl &self, req: &frm_types::FrmCheckoutRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = signifyd::SignifydPaymentsCheckoutRequest::try_from(req)?; - let signifyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(signifyd_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -320,7 +325,7 @@ impl .headers(frm_types::FrmCheckoutType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmCheckoutType::get_request_body( + .set_body(frm_types::FrmCheckoutType::get_request_body( self, req, connectors, )?) .build(), @@ -386,14 +391,9 @@ impl &self, req: &frm_types::FrmTransactionRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = signifyd::SignifydPaymentsTransactionRequest::try_from(req)?; - let signifyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(signifyd_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -411,7 +411,7 @@ impl .headers(frm_types::FrmTransactionType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmTransactionType::get_request_body( + .set_body(frm_types::FrmTransactionType::get_request_body( self, req, connectors, )?) .build(), @@ -477,14 +477,9 @@ impl &self, req: &frm_types::FrmFulfillmentRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = &req.request.fulfillment_request; - let signifyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(signifyd_req)) + ) -> CustomResult { + let req_obj = signifyd::FrmFullfillmentSignifydRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj.clone()))) } fn build_request( @@ -502,7 +497,7 @@ impl .headers(frm_types::FrmFulfillmentType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmFulfillmentType::get_request_body( + .set_body(frm_types::FrmFulfillmentType::get_request_body( self, req, connectors, )?) .build(), @@ -568,14 +563,9 @@ impl &self, req: &frm_types::FrmRecordReturnRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = signifyd::SignifydPaymentsRecordReturnRequest::try_from(req)?; - let signifyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(signifyd_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -593,7 +583,7 @@ impl .headers(frm_types::FrmRecordReturnType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmRecordReturnType::get_request_body( + .set_body(frm_types::FrmRecordReturnType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 1a1b09bd288..66d6f0e48cd 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -11,10 +11,7 @@ use crate::{ AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckRecordReturnRequest, FraudCheckSaleRequest, FraudCheckTransactionRequest, RouterData, }, - core::{ - errors, - fraud_check::types::{self as core_types, FrmFulfillmentRequest}, - }, + core::{errors, fraud_check::types as core_types}, types::{ self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, ResponseId, ResponseRouterData, @@ -356,7 +353,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ #[serde(deny_unknown_fields)] #[serde_with::skip_serializing_none] #[serde(rename_all = "camelCase")] -pub struct FrmFullfillmentSignifydApiRequest { +pub struct FrmFullfillmentSignifydRequest { pub order_id: String, pub fulfillment_status: Option, pub fulfillments: Vec, @@ -391,22 +388,30 @@ pub struct Product { pub item_id: String, } -impl From for FrmFullfillmentSignifydApiRequest { - fn from(req: FrmFulfillmentRequest) -> Self { - Self { - order_id: req.order_id, - fulfillment_status: req.fulfillment_status.map(FulfillmentStatus::from), - fulfillments: req +impl TryFrom<&frm_types::FrmFulfillmentRouterData> for FrmFullfillmentSignifydRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmFulfillmentRouterData) -> Result { + Ok(Self { + order_id: item.request.fulfillment_req.order_id.clone(), + fulfillment_status: item + .request + .fulfillment_req + .fulfillment_status + .clone() + .map(|fulfillment_status| FulfillmentStatus::from(&fulfillment_status)), + fulfillments: item + .request + .fulfillment_req .fulfillments .iter() .map(|f| Fulfillments::from(f.clone())) .collect(), - } + }) } } -impl From for FulfillmentStatus { - fn from(status: core_types::FulfillmentStatus) -> Self { +impl From<&core_types::FulfillmentStatus> for FulfillmentStatus { + fn from(status: &core_types::FulfillmentStatus) -> Self { match status { core_types::FulfillmentStatus::PARTIAL => Self::PARTIAL, core_types::FulfillmentStatus::COMPLETE => Self::COMPLETE, diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index 1f1dee6b9e1..e210a851dfc 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use api_models::enums; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as square; @@ -28,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -162,6 +162,20 @@ impl types::PaymentsResponseData, > for Square { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Square".to_string()) + .into(), + ) + } } #[async_trait::async_trait] @@ -248,15 +262,10 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = square::SquareTokenRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = square::SquareTokenRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -270,7 +279,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -418,15 +427,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = square::SquarePaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = square::SquarePaymentsRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -444,7 +448,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = square::SquareRefundRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + ) -> CustomResult { + let connector_req = square::SquareRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -733,7 +732,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = stax::StaxCustomerRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = stax::StaxCustomerRequest::try_from(req)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -189,7 +184,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -255,15 +250,10 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stax::StaxTokenRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = stax::StaxTokenRequest::try_from(req)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -277,7 +267,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -330,6 +320,20 @@ impl types::PaymentsResponseData, > for Stax { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Stax".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -359,21 +363,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = stax::StaxPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = stax::StaxPaymentsRequest::try_from(&connector_router_data)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -391,7 +390,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -532,12 +531,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -553,7 +547,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = stax::StaxRefundRequest::try_from(&connector_router_data)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + let connector_req = stax::StaxRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -715,7 +704,7 @@ impl ConnectorIntegration for StaxTokenRequest { api::PaymentMethodData::Card(card_data) => { let stax_card_data = StaxTokenizeData { card_exp: card_data - .get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), - person_name: card_data.card_holder_name, + .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?, + person_name: card_data + .card_holder_name + .unwrap_or(Secret::new("".to_string())), card_number: card_data.card_number, card_cvv: card_data.card_cvc, customer_id: Secret::new(customer_id), diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 475105c9ceb..c151c5af455 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::{collections::HashMap, fmt::Debug, ops::Deref}; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -26,7 +27,7 @@ use crate::{ self, api::{self, ConnectorCommon}, }, - utils::{self, crypto, ByteSliceExt, BytesExt, OptionExt}, + utils::{crypto, ByteSliceExt, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -145,15 +146,10 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; - let pre_processing_request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; - Ok(Some(pre_processing_request)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -171,7 +167,7 @@ impl .headers(types::PaymentsPreProcessingType::get_headers( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -273,14 +269,9 @@ impl &self, req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CustomerRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::CustomerRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -298,7 +289,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -404,14 +395,9 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::TokenRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::TokenRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -425,7 +411,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -537,14 +523,9 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CaptureRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::CaptureRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -560,7 +541,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -685,9 +666,6 @@ impl .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( - self, req, connectors, - )?) .build(), )) } @@ -828,19 +806,14 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { match &req.request.payment_method_data { api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { stripe::get_bank_transfer_request_data(req, bank_transfer_data.deref()) } _ => { - let req = stripe::PaymentIntentRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request)) + let connector_req = stripe::PaymentIntentRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } } } @@ -860,7 +833,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -972,14 +945,9 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CancelRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::CancelRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -992,7 +960,7 @@ impl .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .body(types::PaymentsVoidType::get_request_body( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(); @@ -1110,14 +1078,9 @@ impl types::PaymentsResponseData, >, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req = stripe::SetupIntentRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::SetupIntentRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1135,7 +1098,7 @@ impl .url(&Verify::get_url(self, req, connectors)?) .attach_default_headers() .headers(Verify::get_headers(self, req, connectors)?) - .body(Verify::get_request_body(self, req, connectors)?) + .set_body(Verify::get_request_body(self, req, connectors)?) .build(), )) } @@ -1248,14 +1211,9 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::RefundRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + ) -> CustomResult { + let connector_req = stripe::RefundRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1270,7 +1228,7 @@ impl services::ConnectorIntegration CustomResult, errors::ConnectorError> { - let stripe_req = transformers::construct_file_upload_request(req.clone())?; - Ok(Some(stripe_req)) + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = transformers::construct_file_upload_request(req.clone())?; + Ok(RequestContent::FormData(connector_req)) } fn build_request( @@ -1521,8 +1477,9 @@ impl .url(&types::UploadFileType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::UploadFileType::get_headers(self, req, connectors)?) - .form_data(types::UploadFileType::get_request_form_data(self, req)?) - .content_type(services::request::ContentType::FormData) + .set_body(types::UploadFileType::get_request_body( + self, req, connectors, + )?) .build(), )) } @@ -1734,14 +1691,9 @@ impl &self, req: &types::SubmitEvidenceRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let stripe_req = stripe::Evidence::try_from(req)?; - let stripe_req_string = types::RequestBody::log_and_get_request_body( - &stripe_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req_string)) + ) -> CustomResult { + let connector_req = stripe::Evidence::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1756,7 +1708,7 @@ impl .headers(types::SubmitEvidenceType::get_headers( self, req, connectors, )?) - .body(types::SubmitEvidenceType::get_request_body( + .set_body(types::SubmitEvidenceType::get_request_body( self, req, connectors, )?) .build(); @@ -1906,10 +1858,15 @@ impl api::IncomingWebhook for Stripe { Ok(match details.event_data.event_object.object { stripe::WebhookEventObjectType::PaymentIntent => { - match details.event_data.event_object.metadata { + match details + .event_data + .event_object + .metadata + .and_then(|meta_data| meta_data.order_id) + { // if order_id is present - Some(meta_data) => api_models::webhooks::ObjectReferenceId::PaymentId( - api_models::payments::PaymentIdType::PaymentAttemptId(meta_data.order_id), + Some(order_id) => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(order_id), ), // else used connector_transaction_id None => api_models::webhooks::ObjectReferenceId::PaymentId( @@ -1920,10 +1877,15 @@ impl api::IncomingWebhook for Stripe { } } stripe::WebhookEventObjectType::Charge => { - match details.event_data.event_object.metadata { + match details + .event_data + .event_object + .metadata + .and_then(|meta_data| meta_data.order_id) + { // if order_id is present - Some(meta_data) => api_models::webhooks::ObjectReferenceId::PaymentId( - api_models::payments::PaymentIdType::PaymentAttemptId(meta_data.order_id), + Some(order_id) => api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(order_id), ), // else used connector_transaction_id None => api_models::webhooks::ObjectReferenceId::PaymentId( @@ -1956,14 +1918,25 @@ impl api::IncomingWebhook for Stripe { ) } stripe::WebhookEventObjectType::Refund => { - match details.event_data.event_object.metadata { + match details + .event_data + .event_object + .metadata + .clone() + .and_then(|meta_data| meta_data.order_id) + { // if meta_data is present - Some(meta_data) => { + Some(order_id) => { // Issue: 2076 - match meta_data.is_refund_id_as_reference { + match details + .event_data + .event_object + .metadata + .and_then(|meta_data| meta_data.is_refund_id_as_reference) + { // if the order_id is refund_id Some(_) => api_models::webhooks::ObjectReferenceId::RefundId( - api_models::webhooks::RefundIdType::RefundId(meta_data.order_id), + api_models::webhooks::RefundIdType::RefundId(order_id), ), // if the order_id is payment_id // since payment_id was being passed before the deployment of this pr @@ -2004,6 +1977,9 @@ impl api::IncomingWebhook for Stripe { stripe::WebhookEventType::PaymentIntentCanceled => { api::IncomingWebhookEvent::PaymentIntentCancelled } + stripe::WebhookEventType::PaymentIntentAmountCapturableUpdated => { + api::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + } stripe::WebhookEventType::ChargeSucceeded => { if let Some(stripe::WebhookPaymentMethodDetails { payment_method: @@ -2060,7 +2036,6 @@ impl api::IncomingWebhook for Stripe { | stripe::WebhookEventType::ChargeRefunded | stripe::WebhookEventType::PaymentIntentCreated | stripe::WebhookEventType::PaymentIntentProcessing - | stripe::WebhookEventType::PaymentIntentAmountCapturableUpdated | stripe::WebhookEventType::SourceTransactionCreated => { api::IncomingWebhookEvent::EventNotSupported } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index fad029c1c9d..89e18692414 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -5,6 +5,7 @@ use common_utils::{ errors::CustomResult, ext_traits::{ByteSliceExt, BytesExt}, pii::{self, Email}, + request::RequestContent, }; use data_models::mandates::AcceptanceType; use error_stack::{IntoReport, ResultExt}; @@ -26,7 +27,7 @@ use crate::{ storage::enums, transformers::{ForeignFrom, ForeignTryFrom}, }, - utils::{self, OptionExt}, + utils::OptionExt, }; pub struct StripeAuthType { @@ -134,7 +135,7 @@ pub struct PaymentIntentRequest { pub struct StripeMetadata { // merchant_reference_id #[serde(rename(serialize = "metadata[order_id]"))] - pub order_id: String, + pub order_id: Option, // to check whether the order_id is refund_id or payemnt_id // before deployment, order id is set to payemnt_id in refunds but now it is set as refund_id // it is set as string instead of bool because stripe pass it as string even if we set it as bool @@ -1081,16 +1082,30 @@ impl From<&payments::BankDebitBilling> for StripeBillingAddress { } } -impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress { - type Error = errors::ConnectorError; +impl TryFrom<(&payments::BankRedirectData, Option)> for StripeBillingAddress { + type Error = error_stack::Report; - fn try_from(bank_redirection_data: &payments::BankRedirectData) -> Result { + fn try_from( + (bank_redirection_data, is_customer_initiated_mandate_payment): ( + &payments::BankRedirectData, + Option, + ), + ) -> Result { match bank_redirection_data { payments::BankRedirectData::Eps { billing_details, .. - } => Ok(Self { - name: billing_details.billing_name.clone(), - ..Self::default() + } => Ok({ + let billing_data = billing_details.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "billing_details", + }, + )?; + Self { + name: Some(connector_util::BankRedirectBillingData::get_billing_name( + &billing_data, + )?), + ..Self::default() + } }), payments::BankRedirectData::Giropay { billing_details, .. @@ -1100,11 +1115,36 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress { }), payments::BankRedirectData::Ideal { billing_details, .. - } => Ok(Self { - name: billing_details.billing_name.clone(), - email: billing_details.email.clone(), - ..Self::default() - }), + } => { + let billing_name = billing_details + .clone() + .and_then(|billing_data| billing_data.billing_name.clone()); + + let billing_email = billing_details + .clone() + .and_then(|billing_data| billing_data.email.clone()); + match is_customer_initiated_mandate_payment { + Some(true) => Ok(Self { + name: Some(billing_name.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "billing_name", + }, + )?), + + email: Some(billing_email.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "billing_email", + }, + )?), + ..Self::default() + }), + Some(false) | None => Ok(Self { + name: billing_name, + email: billing_email, + ..Self::default() + }), + } + } payments::BankRedirectData::Przelewy24 { billing_details, .. } => Ok(Self { @@ -1229,6 +1269,7 @@ fn create_stripe_payment_method( payment_method_data: &api_models::payments::PaymentMethodData, auth_type: enums::AuthenticationType, payment_method_token: Option, + is_customer_initiated_mandate_payment: Option, ) -> Result< ( StripePaymentMethodData, @@ -1261,7 +1302,10 @@ fn create_stripe_payment_method( )) } payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { - let billing_address = StripeBillingAddress::try_from(bank_redirect_data)?; + let billing_address = StripeBillingAddress::try_from(( + bank_redirect_data, + is_customer_initiated_mandate_payment, + ))?; let pm_type = StripePaymentMethodType::try_from(bank_redirect_data)?; let bank_redirect_data = StripePaymentMethodData::try_from(bank_redirect_data)?; @@ -1750,6 +1794,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { &item.request.payment_method_data, item.auth_type, item.payment_method_token.clone(), + Some(connector_util::PaymentsAuthorizeRequestData::is_customer_initiated_mandate_payment( + &item.request, + )), )?; validate_shipping_address_against_payment_method( @@ -1860,7 +1907,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { statement_descriptor_suffix: item.request.statement_descriptor_suffix.clone(), statement_descriptor: item.request.statement_descriptor.clone(), meta_data: StripeMetadata { - order_id, + order_id: Some(order_id), is_refund_id_as_reference: None, }, return_url: item @@ -1956,6 +2003,7 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { &item.request.payment_method_data, item.auth_type, item.payment_method_token.clone(), + None, )?; Ok(Self { token_data: payment_data.0, @@ -2335,7 +2383,7 @@ pub fn get_connector_metadata( let next_action_response = next_action .and_then(|next_action_response| match next_action_response { StripeNextActionResponse::DisplayBankTransferInstructions(response) => { - let bank_instructions = response.financial_addresses.get(0); + let bank_instructions = response.financial_addresses.first(); let (sepa_bank_instructions, bacs_bank_instructions) = bank_instructions.map_or((None, None), |financial_address| { ( @@ -2695,7 +2743,7 @@ impl TryFrom<&types::RefundsRouterData> for RefundRequest { amount: Some(amount), payment_intent, meta_data: StripeMetadata { - order_id: item.request.refund_id.clone(), + order_id: Some(item.request.refund_id.clone()), is_refund_id_as_reference: Some("true".to_string()), }, }) @@ -3202,7 +3250,7 @@ pub struct WebhookPaymentMethodDetails { pub payment_method: WebhookPaymentMethodType, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Deserialize)] pub struct WebhookEventObjectData { pub id: String, pub object: WebhookEventObjectType, @@ -3217,7 +3265,7 @@ pub struct WebhookEventObjectData { pub metadata: Option, } -#[derive(Debug, Deserialize, strum::Display)] +#[derive(Debug, Clone, Deserialize, strum::Display)] #[serde(rename_all = "snake_case")] pub enum WebhookEventObjectType { PaymentIntent, @@ -3267,7 +3315,7 @@ pub enum WebhookEventType { PaymentIntentProcessing, #[serde(rename = "payment_intent.requires_action")] PaymentIntentRequiresAction, - #[serde(rename = "amount_capturable_updated")] + #[serde(rename = "payment_intent.amount_capturable_updated")] PaymentIntentAmountCapturableUpdated, #[serde(rename = "source.chargeable")] SourceChargeable, @@ -3279,7 +3327,7 @@ pub enum WebhookEventType { Unknown, } -#[derive(Debug, Serialize, strum::Display, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, strum::Display, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum WebhookEventStatus { WarningNeedsResponse, @@ -3303,7 +3351,7 @@ pub enum WebhookEventStatus { Unknown, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Debug, Clone, Deserialize, PartialEq)] pub struct EvidenceDetails { #[serde(with = "common_utils::custom_serde::timestamp")] pub due_by: PrimitiveDateTime, @@ -3424,26 +3472,16 @@ pub struct StripeGpayToken { pub fn get_bank_transfer_request_data( req: &types::PaymentsAuthorizeRouterData, bank_transfer_data: &api_models::payments::BankTransferData, -) -> CustomResult, errors::ConnectorError> { +) -> CustomResult { match bank_transfer_data { api_models::payments::BankTransferData::AchBankTransfer { .. } | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { let req = ChargesRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request)) + Ok(RequestContent::FormUrlEncoded(Box::new(req))) } _ => { let req = PaymentIntentRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request)) + Ok(RequestContent::FormUrlEncoded(Box::new(req))) } } } @@ -3658,7 +3696,10 @@ mod test_validate_shipping_address_against_payment_method { assert!(result.is_err()); let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned(); assert_eq!(missing_fields.len(), 1); - assert_eq!(missing_fields[0], "shipping.address.first_name"); + assert_eq!( + *missing_fields.first().unwrap(), + "shipping.address.first_name" + ); } #[test] @@ -3683,7 +3724,7 @@ mod test_validate_shipping_address_against_payment_method { assert!(result.is_err()); let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned(); assert_eq!(missing_fields.len(), 1); - assert_eq!(missing_fields[0], "shipping.address.line1"); + assert_eq!(*missing_fields.first().unwrap(), "shipping.address.line1"); } #[test] @@ -3708,7 +3749,7 @@ mod test_validate_shipping_address_against_payment_method { assert!(result.is_err()); let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned(); assert_eq!(missing_fields.len(), 1); - assert_eq!(missing_fields[0], "shipping.address.country"); + assert_eq!(*missing_fields.first().unwrap(), "shipping.address.country"); } #[test] @@ -3732,7 +3773,7 @@ mod test_validate_shipping_address_against_payment_method { assert!(result.is_err()); let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned(); assert_eq!(missing_fields.len(), 1); - assert_eq!(missing_fields[0], "shipping.address.zip"); + assert_eq!(*missing_fields.first().unwrap(), "shipping.address.zip"); } #[test] diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 286eaf9cb54..61ee2e2659d 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -3,7 +3,9 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use common_utils::{ + crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent, +}; use error_stack::{IntoReport, Report, ResultExt}; use masking::PeekInterface; use transformers as trustpay; @@ -152,11 +154,7 @@ impl ConnectorCommon for Trustpay { } } -impl ConnectorValidation for Trustpay { - fn validate_if_surcharge_implemented(&self) -> CustomResult<(), errors::ConnectorError> { - Ok(()) - } -} +impl ConnectorValidation for Trustpay {} impl api::Payment for Trustpay {} @@ -180,6 +178,20 @@ impl types::PaymentsResponseData, > for Trustpay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Trustpay".to_string()) + .into(), + ) + } } impl api::PaymentVoid for Trustpay {} @@ -240,14 +252,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = trustpay::TrustpayAuthUpdateRequest::try_from(req)?; - let trustpay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(trustpay_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -261,7 +268,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let currency = req.request.get_currency()?; - let amount = req - .request - .surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.final_amount) - .unwrap_or(req.request.get_amount()?); + let amount = req.request.get_amount()?; let connector_router_data = trustpay::TrustpayRouterData::try_from(( &self.get_currency_unit(), currency, amount, req, ))?; - let create_intent_req = + let connector_req = trustpay::TrustpayCreateIntentRequest::try_from(&connector_router_data)?; - let trustpay_req = types::RequestBody::log_and_get_request_body( - &create_intent_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(trustpay_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -472,7 +469,7 @@ impl .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -551,13 +548,8 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let amount = req - .request - .surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.final_amount) - .unwrap_or(req.request.amount); + ) -> CustomResult { + let amount = req.request.amount; let connector_router_data = trustpay::TrustpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -565,21 +557,12 @@ impl ConnectorIntegration { - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)? + Ok(RequestContent::Json(Box::new(connector_req))) } - _ => types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - }; - Ok(Some(trustpay_req_string)) + _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))), + } } fn build_request( @@ -597,7 +580,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = trustpay::TrustpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -677,22 +660,12 @@ impl ConnectorIntegration { - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)? + Ok(RequestContent::Json(Box::new(connector_req))) } - _ => - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - }; - Ok(Some(trustpay_req_string)) + _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))), + } } fn build_request( @@ -707,7 +680,7 @@ impl ConnectorIntegration> currency: currency.to_string(), init_apple_pay: is_apple_pay, init_google_pay: is_google_pay, - reference: item.router_data.payment_id.clone(), + reference: item.router_data.connector_request_reference_id.clone(), }) } } @@ -1181,7 +1181,7 @@ pub fn get_apple_pay_session( }, ), payment_request_data: Some(api_models::payments::ApplePayPaymentRequest { - country_code: apple_pay_init_result.country_code, + country_code: Some(apple_pay_init_result.country_code), currency_code: apple_pay_init_result.currency_code, supported_networks: Some(apple_pay_init_result.supported_networks.clone()), merchant_capabilities: Some( @@ -1700,7 +1700,7 @@ impl TryFrom for diesel_models::enums::RefundStatus { #[serde(rename_all = "PascalCase")] pub struct WebhookReferences { pub merchant_reference: String, - pub payment_id: String, + pub payment_id: Option, pub payment_request_id: Option, } diff --git a/crates/router/src/connector/tsys.rs b/crates/router/src/connector/tsys.rs index 0143f5855ad..8c19f9f823a 100644 --- a/crates/router/src/connector/tsys.rs +++ b/crates/router/src/connector/tsys.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as tsys; @@ -21,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -112,6 +113,20 @@ impl types::PaymentsResponseData, > for Tsys { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Tsys".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -144,14 +159,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysPaymentsRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + ) -> CustomResult { + let connector_req = tsys::TsysPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -169,7 +179,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysSyncRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + ) -> CustomResult { + let connector_req = tsys::TsysSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -251,7 +256,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -335,7 +335,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysPaymentsCancelRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + ) -> CustomResult { + let connector_req = tsys::TsysPaymentsCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -414,7 +409,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysRefundRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + ) -> CustomResult { + let connector_req = tsys::TsysRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -495,7 +485,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysSyncRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + ) -> CustomResult { + let connector_req = tsys::TsysSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -575,7 +560,7 @@ impl ConnectorIntegration for TsysPaymentsRequest { currency_code: item.request.currency, card_number: ccard.card_number.clone(), expiration_date: ccard - .get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned()), + .get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned())?, cvv2: ccard.card_cvc, terminal_capability: "ICC_CHIP_READ_ONLY".to_string(), terminal_operating_environment: "ON_MERCHANT_PREMISES_ATTENDED".to_string(), diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 3990fc9c7e4..8f028e37a9e 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -10,6 +10,7 @@ use common_utils::{ errors::ReportSwitchExt, pii::{self, Email, IpAddress}, }; +use data_models::payments::payment_attempt::PaymentAttempt; use diesel_models::enums; use error_stack::{report, IntoReport, ResultExt}; use masking::{ExposeInterface, Secret}; @@ -22,13 +23,13 @@ use crate::types::{fraud_check, storage::enums as storage_enums}; use crate::{ consts, core::{ - errors::{self, CustomResult}, + errors::{self, ApiErrorResponse, CustomResult}, payments::PaymentData, }, pii::PeekInterface, types::{ - self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom, - ApplePayPredecryptData, PaymentsCancelData, ResponseId, + self, api, transformers::ForeignTryFrom, ApplePayPredecryptData, BrowserInformation, + PaymentsCancelData, ResponseId, }, utils::{OptionExt, ValueExt}, }; @@ -67,6 +68,8 @@ pub trait RouterData { fn get_return_url(&self) -> Result; fn get_billing_address(&self) -> Result<&api::AddressDetails, Error>; fn get_shipping_address(&self) -> Result<&api::AddressDetails, Error>; + fn get_billing_address_with_phone_number(&self) -> Result<&api::Address, Error>; + fn get_shipping_address_with_phone_number(&self) -> Result<&api::Address, Error>; fn get_connector_meta(&self) -> Result; fn get_session_token(&self) -> Result; fn to_connector_meta(&self) -> Result @@ -114,7 +117,7 @@ where } enums::AttemptStatus::Charged => { let captured_amount = - types::Capturable::get_capture_amount(&self.request, payment_data); + types::Capturable::get_captured_amount(&self.request, payment_data); let total_capturable_amount = payment_data.payment_attempt.get_total_amount(); if Some(total_capturable_amount) == captured_amount { enums::AttemptStatus::Charged @@ -176,6 +179,13 @@ impl RouterData for types::RouterData Result<&api::Address, Error> { + self.address + .billing + .as_ref() + .ok_or_else(missing_field_err("billing")) + } fn get_connector_meta(&self) -> Result { self.connector_meta_data .clone() @@ -211,6 +221,14 @@ impl RouterData for types::RouterData Result<&api::Address, Error> { + self.address + .shipping + .as_ref() + .ok_or_else(missing_field_err("shipping")) + } + fn get_payment_method_token(&self) -> Result { self.payment_method_token .clone() @@ -254,7 +272,8 @@ pub trait PaymentsPreProcessingData { fn get_order_details(&self) -> Result, Error>; fn get_webhook_url(&self) -> Result; fn get_return_url(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; + fn get_complete_authorize_url(&self) -> Result; } impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { @@ -294,36 +313,53 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { .clone() .ok_or_else(missing_field_err("return_url")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_complete_authorize_url(&self) -> Result { + self.complete_authorize_url + .clone() + .ok_or_else(missing_field_err("complete_authorize_url")) + } } pub trait PaymentsCaptureRequestData { fn is_multiple_capture(&self) -> bool; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsCaptureRequestData for types::PaymentsCaptureData { fn is_multiple_capture(&self) -> bool { self.multiple_capture_data.is_some() } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) } } +pub trait RevokeMandateRequestData { + fn get_connector_mandate_id(&self) -> Result; +} + +impl RevokeMandateRequestData for types::MandateRevokeRequestData { + fn get_connector_mandate_id(&self) -> Result { + self.connector_mandate_id + .clone() + .ok_or_else(missing_field_err("connector_mandate_id")) + } +} + pub trait PaymentsSetupMandateRequestData { - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; fn get_email(&self) -> Result; } impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -335,12 +371,13 @@ impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { pub trait PaymentsAuthorizeRequestData { fn is_auto_capture(&self) -> Result; fn get_email(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_card(&self) -> Result; fn get_return_url(&self) -> Result; fn connector_mandate_id(&self) -> Option; fn is_mandate_payment(&self) -> bool; + fn is_customer_initiated_mandate_payment(&self) -> bool; fn get_webhook_url(&self) -> Result; fn get_router_return_url(&self) -> Result; fn is_wallet(&self) -> bool; @@ -349,14 +386,18 @@ pub trait PaymentsAuthorizeRequestData { fn get_connector_mandate_id(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn get_ip_address_as_optional(&self) -> Option>; + fn get_original_amount(&self) -> i64; + fn get_surcharge_amount(&self) -> Option; + fn get_tax_on_surcharge_amount(&self) -> Option; + fn get_total_surcharge_amount(&self) -> Option; } pub trait PaymentMethodTokenizationRequestData { - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentMethodTokenizationRequestData for types::PaymentMethodTokenizationData { - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -374,7 +415,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -455,6 +496,31 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { .map(|ip| Secret::new(ip.to_string())) }) } + fn get_original_amount(&self) -> i64 { + self.surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.original_amount) + .unwrap_or(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) + } + fn get_total_surcharge_amount(&self) -> Option { + self.surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) + } + + fn is_customer_initiated_mandate_payment(&self) -> bool { + self.setup_mandate_details.is_some() + } } pub trait ConnectorCustomerData { @@ -480,7 +546,7 @@ pub trait BrowserInformationData { fn get_ip_address(&self) -> Result, Error>; } -impl BrowserInformationData for types::BrowserInformation { +impl BrowserInformationData for BrowserInformation { fn get_ip_address(&self) -> Result, Error> { let ip_address = self .ip_address @@ -532,6 +598,7 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn is_auto_capture(&self) -> Result; fn get_email(&self) -> Result; fn get_redirect_response_payload(&self) -> Result; + fn get_complete_authorize_url(&self) -> Result; } impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { @@ -556,6 +623,11 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { .into(), ) } + fn get_complete_authorize_url(&self) -> Result { + self.complete_authorize_url + .clone() + .ok_or_else(missing_field_err("complete_authorize_url")) + } } pub trait PaymentsSyncRequestData { @@ -588,7 +660,7 @@ pub trait PaymentsCancelRequestData { fn get_amount(&self) -> Result; fn get_currency(&self) -> Result; fn get_cancellation_reason(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsCancelRequestData for PaymentsCancelData { @@ -603,7 +675,7 @@ impl PaymentsCancelRequestData for PaymentsCancelData { .clone() .ok_or_else(missing_field_err("cancellation_reason")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -613,7 +685,7 @@ impl PaymentsCancelRequestData for PaymentsCancelData { pub trait RefundsRequestData { fn get_connector_refund_id(&self) -> Result; fn get_webhook_url(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl RefundsRequestData for types::RefundsData { @@ -629,7 +701,7 @@ impl RefundsRequestData for types::RefundsData { .clone() .ok_or_else(missing_field_err("webhook_url")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -714,23 +786,27 @@ pub enum CardIssuer { } pub trait CardData { - fn get_card_expiry_year_2_digit(&self) -> Secret; + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError>; fn get_card_issuer(&self) -> Result; fn get_card_expiry_month_year_2_digit_with_delimiter( &self, delimiter: String, - ) -> Secret; + ) -> Result, errors::ConnectorError>; fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret; fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret; fn get_expiry_year_4_digit(&self) -> Secret; - fn get_expiry_date_as_yymm(&self) -> Secret; + fn get_expiry_date_as_yymm(&self) -> Result, errors::ConnectorError>; } impl CardData for api::Card { - fn get_card_expiry_year_2_digit(&self) -> Secret { + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { let binding = self.card_exp_year.clone(); let year = binding.peek(); - Secret::new(year[year.len() - 2..].to_string()) + Ok(Secret::new( + year.get(year.len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_string(), + )) } fn get_card_issuer(&self) -> Result { get_card_issuer(self.card_number.peek()) @@ -738,14 +814,14 @@ impl CardData for api::Card { fn get_card_expiry_month_year_2_digit_with_delimiter( &self, delimiter: String, - ) -> Secret { - let year = self.get_card_expiry_year_2_digit(); - Secret::new(format!( + ) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?; + Ok(Secret::new(format!( "{}{}{}", - self.card_exp_month.peek().clone(), + self.card_exp_month.peek(), delimiter, year.peek() - )) + ))) } fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret { let year = self.get_expiry_year_4_digit(); @@ -753,14 +829,14 @@ impl CardData for api::Card { "{}{}{}", year.peek(), delimiter, - self.card_exp_month.peek().clone() + self.card_exp_month.peek() )) } fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret { let year = self.get_expiry_year_4_digit(); Secret::new(format!( "{}{}{}", - self.card_exp_month.peek().clone(), + self.card_exp_month.peek(), delimiter, year.peek() )) @@ -772,10 +848,10 @@ impl CardData for api::Card { } Secret::new(year) } - fn get_expiry_date_as_yymm(&self) -> Secret { - let year = self.get_card_expiry_year_2_digit().expose(); + fn get_expiry_date_as_yymm(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); let month = self.card_exp_month.clone().expose(); - Secret::new(format!("{year}{month}")) + Ok(Secret::new(format!("{year}{month}"))) } } @@ -1147,7 +1223,7 @@ where { let connector_meta_secret = connector_meta.ok_or_else(missing_field_err("connector_meta_data"))?; - let json = connector_meta_secret.peek().clone(); + let json = connector_meta_secret.expose(); json.parse_value(std::any::type_name::()).switch() } @@ -1465,86 +1541,6 @@ pub fn get_error_code_error_message_based_on_priority( .cloned() } -#[cfg(test)] -mod error_code_error_message_tests { - #![allow(clippy::unwrap_used)] - use super::*; - - struct TestConnector; - - impl ConnectorErrorTypeMapping for TestConnector { - fn get_connector_error_type( - &self, - error_code: String, - error_message: String, - ) -> ConnectorErrorType { - match (error_code.as_str(), error_message.as_str()) { - ("01", "INVALID_MERCHANT") => ConnectorErrorType::BusinessError, - ("03", "INVALID_CVV") => ConnectorErrorType::UserError, - ("04", "04") => ConnectorErrorType::TechnicalError, - _ => ConnectorErrorType::UnknownError, - } - } - } - - #[test] - fn test_get_error_code_error_message_based_on_priority() { - let error_code_message_list_unknown = vec![ - ErrorCodeAndMessage { - error_code: "01".to_string(), - error_message: "INVALID_MERCHANT".to_string(), - }, - ErrorCodeAndMessage { - error_code: "05".to_string(), - error_message: "05".to_string(), - }, - ErrorCodeAndMessage { - error_code: "03".to_string(), - error_message: "INVALID_CVV".to_string(), - }, - ErrorCodeAndMessage { - error_code: "04".to_string(), - error_message: "04".to_string(), - }, - ]; - let error_code_message_list_user = vec![ - ErrorCodeAndMessage { - error_code: "01".to_string(), - error_message: "INVALID_MERCHANT".to_string(), - }, - ErrorCodeAndMessage { - error_code: "03".to_string(), - error_message: "INVALID_CVV".to_string(), - }, - ]; - let error_code_error_message_unknown = get_error_code_error_message_based_on_priority( - TestConnector, - error_code_message_list_unknown, - ); - let error_code_error_message_user = get_error_code_error_message_based_on_priority( - TestConnector, - error_code_message_list_user, - ); - let error_code_error_message_none = - get_error_code_error_message_based_on_priority(TestConnector, vec![]); - assert_eq!( - error_code_error_message_unknown, - Some(ErrorCodeAndMessage { - error_code: "05".to_string(), - error_message: "05".to_string(), - }) - ); - assert_eq!( - error_code_error_message_user, - Some(ErrorCodeAndMessage { - error_code: "03".to_string(), - error_message: "INVALID_CVV".to_string(), - }) - ); - assert_eq!(error_code_error_message_none, None); - } -} - pub trait MultipleCaptureSyncResponse { fn get_connector_capture_id(&self) -> String; fn get_capture_attempt_status(&self) -> enums::AttemptStatus; @@ -1587,6 +1583,12 @@ pub fn is_manual_capture(capture_method: Option) -> bool { || capture_method == Some(enums::CaptureMethod::ManualMultiple) } +pub fn generate_random_bytes(length: usize) -> Vec { + // returns random bytes of length n + let mut rng = rand::thread_rng(); + (0..length).map(|_| rand::Rng::gen(&mut rng)).collect() +} + pub fn validate_currency( request_currency: types::storage::enums::Currency, merchant_config_currency: Option, @@ -1652,3 +1654,202 @@ impl FraudCheckRecordReturnRequest for fraud_check::FraudCheckRecordReturnData { self.currency.ok_or_else(missing_field_err("currency")) } } + +pub trait AccessPaymentAttemptInfo { + fn get_browser_info( + &self, + ) -> Result, error_stack::Report>; +} + +impl AccessPaymentAttemptInfo for PaymentAttempt { + fn get_browser_info( + &self, + ) -> Result, error_stack::Report> { + self.browser_info + .clone() + .map(|b| b.parse_value("BrowserInformation")) + .transpose() + .change_context(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + }) + } +} + +pub trait PaymentsAttemptData { + fn get_browser_info(&self) + -> Result>; +} + +impl PaymentsAttemptData for PaymentAttempt { + fn get_browser_info( + &self, + ) -> Result> { + self.browser_info + .clone() + .ok_or(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + })? + .parse_value::("BrowserInformation") + .change_context(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + }) + } +} + +#[cfg(feature = "frm")] +pub trait FrmTransactionRouterDataRequest { + fn is_payment_successful(&self) -> Option; +} + +#[cfg(feature = "frm")] +impl FrmTransactionRouterDataRequest for fraud_check::FrmTransactionRouterData { + fn is_payment_successful(&self) -> Option { + match self.status { + storage_enums::AttemptStatus::AuthenticationFailed + | storage_enums::AttemptStatus::RouterDeclined + | storage_enums::AttemptStatus::AuthorizationFailed + | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::CaptureFailed + | storage_enums::AttemptStatus::Failure + | storage_enums::AttemptStatus::AutoRefunded => Some(false), + + storage_enums::AttemptStatus::AuthenticationSuccessful + | storage_enums::AttemptStatus::PartialChargedAndChargeable + | storage_enums::AttemptStatus::Authorized + | storage_enums::AttemptStatus::Charged => Some(true), + + storage_enums::AttemptStatus::Started + | storage_enums::AttemptStatus::AuthenticationPending + | storage_enums::AttemptStatus::Authorizing + | storage_enums::AttemptStatus::CodInitiated + | storage_enums::AttemptStatus::VoidInitiated + | storage_enums::AttemptStatus::CaptureInitiated + | storage_enums::AttemptStatus::VoidFailed + | storage_enums::AttemptStatus::PartialCharged + | storage_enums::AttemptStatus::Unresolved + | storage_enums::AttemptStatus::Pending + | storage_enums::AttemptStatus::PaymentMethodAwaited + | storage_enums::AttemptStatus::ConfirmationAwaited + | storage_enums::AttemptStatus::DeviceDataCollectionPending => None, + } + } +} + +pub fn is_payment_failure(status: enums::AttemptStatus) -> bool { + match status { + common_enums::AttemptStatus::AuthenticationFailed + | common_enums::AttemptStatus::AuthorizationFailed + | common_enums::AttemptStatus::CaptureFailed + | common_enums::AttemptStatus::VoidFailed + | common_enums::AttemptStatus::Failure => true, + common_enums::AttemptStatus::Started + | common_enums::AttemptStatus::RouterDeclined + | common_enums::AttemptStatus::AuthenticationPending + | common_enums::AttemptStatus::AuthenticationSuccessful + | common_enums::AttemptStatus::Authorized + | common_enums::AttemptStatus::Charged + | common_enums::AttemptStatus::Authorizing + | common_enums::AttemptStatus::CodInitiated + | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidInitiated + | common_enums::AttemptStatus::CaptureInitiated + | common_enums::AttemptStatus::AutoRefunded + | common_enums::AttemptStatus::PartialCharged + | common_enums::AttemptStatus::PartialChargedAndChargeable + | common_enums::AttemptStatus::Unresolved + | common_enums::AttemptStatus::Pending + | common_enums::AttemptStatus::PaymentMethodAwaited + | common_enums::AttemptStatus::ConfirmationAwaited + | common_enums::AttemptStatus::DeviceDataCollectionPending => false, + } +} + +pub fn is_refund_failure(status: enums::RefundStatus) -> bool { + match status { + common_enums::RefundStatus::Failure | common_enums::RefundStatus::TransactionFailure => { + true + } + common_enums::RefundStatus::ManualReview + | common_enums::RefundStatus::Pending + | common_enums::RefundStatus::Success => false, + } +} +#[cfg(test)] +mod error_code_error_message_tests { + #![allow(clippy::unwrap_used)] + use super::*; + + struct TestConnector; + + impl ConnectorErrorTypeMapping for TestConnector { + fn get_connector_error_type( + &self, + error_code: String, + error_message: String, + ) -> ConnectorErrorType { + match (error_code.as_str(), error_message.as_str()) { + ("01", "INVALID_MERCHANT") => ConnectorErrorType::BusinessError, + ("03", "INVALID_CVV") => ConnectorErrorType::UserError, + ("04", "04") => ConnectorErrorType::TechnicalError, + _ => ConnectorErrorType::UnknownError, + } + } + } + + #[test] + fn test_get_error_code_error_message_based_on_priority() { + let error_code_message_list_unknown = vec![ + ErrorCodeAndMessage { + error_code: "01".to_string(), + error_message: "INVALID_MERCHANT".to_string(), + }, + ErrorCodeAndMessage { + error_code: "05".to_string(), + error_message: "05".to_string(), + }, + ErrorCodeAndMessage { + error_code: "03".to_string(), + error_message: "INVALID_CVV".to_string(), + }, + ErrorCodeAndMessage { + error_code: "04".to_string(), + error_message: "04".to_string(), + }, + ]; + let error_code_message_list_user = vec![ + ErrorCodeAndMessage { + error_code: "01".to_string(), + error_message: "INVALID_MERCHANT".to_string(), + }, + ErrorCodeAndMessage { + error_code: "03".to_string(), + error_message: "INVALID_CVV".to_string(), + }, + ]; + let error_code_error_message_unknown = get_error_code_error_message_based_on_priority( + TestConnector, + error_code_message_list_unknown, + ); + let error_code_error_message_user = get_error_code_error_message_based_on_priority( + TestConnector, + error_code_message_list_user, + ); + let error_code_error_message_none = + get_error_code_error_message_based_on_priority(TestConnector, vec![]); + assert_eq!( + error_code_error_message_unknown, + Some(ErrorCodeAndMessage { + error_code: "05".to_string(), + error_message: "05".to_string(), + }) + ); + assert_eq!( + error_code_error_message_user, + Some(ErrorCodeAndMessage { + error_code: "03".to_string(), + error_message: "INVALID_CVV".to_string(), + }) + ); + assert_eq!(error_code_error_message_none, None); + } +} diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 14b5c67804f..f125f90d93a 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -2,10 +2,13 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; use transformers as volt; +use self::transformers::webhook_headers; +use super::utils; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -20,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -176,15 +179,10 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = volt::VoltAuthUpdateRequest::try_from(req)?; - let volt_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + ) -> CustomResult { + let connector_req = volt::VoltAuthUpdateRequest::try_from(req)?; - Ok(Some(volt_req)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -198,7 +196,7 @@ impl ConnectorIntegration CustomResult { - self.build_error_response(res) + // auth error have different structure than common error + let response: volt::VoltAuthErrorResponse = res + .response + .parse_struct("VoltAuthErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code.to_string(), + message: response.message.clone(), + reason: Some(response.message), + attempt_status: None, + connector_transaction_id: None, + }) } } @@ -238,6 +249,20 @@ impl types::PaymentsResponseData, > for Volt { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Volt".to_string()) + .into(), + ) + } } impl ConnectorIntegration @@ -267,20 +292,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = volt::VoltRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = volt::VoltPaymentsRequest::try_from(&connector_router_data)?; - let volt_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(volt_req)) + let connector_req = volt::VoltPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -298,7 +318,7 @@ impl ConnectorIntegration CustomResult { - let response: volt::VoltPsyncResponse = res + let response: volt::VoltPaymentsResponseData = res .response .parse_struct("volt PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -426,7 +446,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -443,7 +463,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = volt::VoltRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = volt::VoltRefundRequest::try_from(&connector_router_data)?; - let volt_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(volt_req)) + let connector_req = volt::VoltRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -536,7 +551,7 @@ impl ConnectorIntegration, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let signature = + utils::get_header_key_value(webhook_headers::X_VOLT_SIGNED, request.headers) + .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; + + hex::decode(signature) + .into_report() + .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid) + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let x_volt_timed = + utils::get_header_key_value(webhook_headers::X_VOLT_TIMED, request.headers)?; + let user_agent = utils::get_header_key_value(webhook_headers::USER_AGENT, request.headers)?; + let version = user_agent + .split('/') + .last() + .ok_or(errors::ConnectorError::WebhookSourceVerificationFailed)?; + Ok(format!( + "{}|{}|{}", + String::from_utf8_lossy(request.body), + x_volt_timed, + version + ) + .into_bytes()) + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + let parsed_webhook_response = request + .body + .parse_struct::("VoltRefundWebhookBodyReference") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + + match parsed_webhook_response { + volt::WebhookResponse::Payment(payment_response) => { + let reference = match payment_response.merchant_internal_reference { + Some(merchant_internal_reference) => { + api_models::payments::PaymentIdType::PaymentAttemptId( + merchant_internal_reference, + ) + } + None => api_models::payments::PaymentIdType::ConnectorTransactionId( + payment_response.payment, + ), + }; + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + reference, + )) + } + volt::WebhookResponse::Refund(refund_response) => { + let refund_reference = match refund_response.external_reference { + Some(external_reference) => { + api_models::webhooks::RefundIdType::RefundId(external_reference) + } + None => api_models::webhooks::RefundIdType::ConnectorRefundId( + refund_response.refund, + ), + }; + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + refund_reference, + )) + } + } } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + if request.body.is_empty() { + Ok(api::IncomingWebhookEvent::EndpointVerification) + } else { + let payload: volt::VoltWebhookBodyEventType = request + .body + .parse_struct("VoltWebhookBodyEventType") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api::IncomingWebhookEvent::from(payload)) + } } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + let details: volt::VoltWebhookObjectResource = request + .body + .parse_struct("VoltWebhookObjectResource") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(details)) } } diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs index cea56feb714..b1d77f3416f 100644 --- a/crates/router/src/connector/volt/transformers.rs +++ b/crates/router/src/connector/volt/transformers.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{self, AddressDetailsData, RouterData}, + consts, core::errors, services, types::{self, api, storage::enums as storage_enums}, @@ -41,6 +42,12 @@ impl } } +pub mod webhook_headers { + pub const X_VOLT_SIGNED: &str = "X-Volt-Signed"; + pub const X_VOLT_TIMED: &str = "X-Volt-Timed"; + pub const USER_AGENT: &str = "User-Agent"; +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct VoltPaymentsRequest { @@ -50,7 +57,6 @@ pub struct VoltPaymentsRequest { transaction_type: TransactionType, merchant_internal_reference: String, shopper: ShopperDetails, - notification_url: Option, payment_success_url: Option, payment_failure_url: Option, payment_pending_url: Option, @@ -91,7 +97,6 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme let payment_failure_url = item.router_data.request.router_return_url.clone(); let payment_pending_url = item.router_data.request.router_return_url.clone(); let payment_cancel_url = item.router_data.request.router_return_url.clone(); - let notification_url = item.router_data.request.webhook_url.clone(); let address = item.router_data.get_billing_address()?; let shopper = ShopperDetails { email: item.router_data.request.email.clone(), @@ -109,7 +114,6 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme payment_failure_url, payment_pending_url, payment_cancel_url, - notification_url, shopper, transaction_type, }) @@ -291,8 +295,9 @@ impl } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Clone, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[derive(strum::Display)] pub enum VoltPaymentStatus { NewPayment, Completed, @@ -309,7 +314,15 @@ pub enum VoltPaymentStatus { Failed, Settled, } -#[derive(Debug, Deserialize)] + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum VoltPaymentsResponseData { + PsyncResponse(VoltPsyncResponse), + WebhookResponse(VoltPaymentWebhookObjectResource), +} + +#[derive(Debug, Serialize, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VoltPsyncResponse { status: VoltPaymentStatus, @@ -317,35 +330,112 @@ pub struct VoltPsyncResponse { merchant_internal_reference: Option, } -impl TryFrom> +impl + TryFrom> for types::RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: types::ResponseRouterData< + F, + VoltPaymentsResponseData, + T, + types::PaymentsResponseData, + >, ) -> Result { - Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .merchant_internal_reference - .or(Some(item.response.id)), - incremental_authorization_allowed: None, - }), - ..item.data - }) + match item.response { + VoltPaymentsResponseData::PsyncResponse(payment_response) => { + let status = enums::AttemptStatus::from(payment_response.status.clone()); + Ok(Self { + status, + response: if is_payment_failure(status) { + Err(types::ErrorResponse { + code: payment_response.status.clone().to_string(), + message: payment_response.status.clone().to_string(), + reason: Some(payment_response.status.to_string()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(payment_response.id), + }) + } else { + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + payment_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: payment_response + .merchant_internal_reference + .or(Some(payment_response.id)), + incremental_authorization_allowed: None, + }) + }, + ..item.data + }) + } + VoltPaymentsResponseData::WebhookResponse(webhook_response) => { + let detailed_status = webhook_response.detailed_status.clone(); + let status = enums::AttemptStatus::from(webhook_response.status); + Ok(Self { + status, + response: if is_payment_failure(status) { + Err(types::ErrorResponse { + code: detailed_status + .clone() + .map(|volt_status| volt_status.to_string()) + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_owned()), + message: detailed_status + .clone() + .map(|volt_status| volt_status.to_string()) + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_owned()), + reason: detailed_status + .clone() + .map(|volt_status| volt_status.to_string()), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(webhook_response.payment.clone()), + }) + } else { + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + webhook_response.payment.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: webhook_response + .merchant_internal_reference + .or(Some(webhook_response.payment)), + incremental_authorization_allowed: None, + }) + }, + ..item.data + }) + } + } + } +} +impl From for enums::AttemptStatus { + fn from(status: VoltWebhookPaymentStatus) -> Self { + match status { + VoltWebhookPaymentStatus::Completed | VoltWebhookPaymentStatus::Received => { + Self::Charged + } + VoltWebhookPaymentStatus::Failed | VoltWebhookPaymentStatus::NotReceived => { + Self::Failure + } + VoltWebhookPaymentStatus::Pending => Self::Pending, + } } } // REFUND : // Type definition for RefundRequest #[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct VoltRefundRequest { pub amount: i64, pub external_reference: String, @@ -361,28 +451,6 @@ impl TryFrom<&VoltRouterData<&types::RefundsRouterData>> for VoltRefundReq } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping - } - } -} - #[derive(Default, Debug, Clone, Deserialize)] pub struct RefundResponse { id: String, @@ -405,11 +473,137 @@ impl TryFrom> } } +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltPaymentWebhookBodyReference { + pub payment: String, + pub merchant_internal_reference: Option, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltRefundWebhookBodyReference { + pub refund: String, + pub external_reference: Option, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum WebhookResponse { + // the enum order shouldn't be changed as this is being used during serialization and deserialization + Refund(VoltRefundWebhookBodyReference), + Payment(VoltPaymentWebhookBodyReference), +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum VoltWebhookBodyEventType { + Payment(VoltPaymentsWebhookBodyEventType), + Refund(VoltRefundsWebhookBodyEventType), +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltPaymentsWebhookBodyEventType { + pub status: VoltWebhookPaymentStatus, + pub detailed_status: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltRefundsWebhookBodyEventType { + pub status: VoltWebhookRefundsStatus, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum VoltWebhookObjectResource { + Payment(VoltPaymentWebhookObjectResource), + Refund(VoltRefundWebhookObjectResource), +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltPaymentWebhookObjectResource { + pub payment: String, + pub merchant_internal_reference: Option, + pub status: VoltWebhookPaymentStatus, + pub detailed_status: Option, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VoltRefundWebhookObjectResource { + pub refund: String, + pub external_reference: Option, + pub status: VoltWebhookRefundsStatus, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum VoltWebhookPaymentStatus { + Completed, + Failed, + Pending, + Received, + NotReceived, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum VoltWebhookRefundsStatus { + RefundConfirmed, + RefundFailed, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[derive(strum::Display)] +pub enum VoltDetailedStatus { + RefusedByRisk, + RefusedByBank, + ErrorAtBank, + CancelledByUser, + AbandonedByUser, + Failed, + Completed, + BankRedirect, + DelayedAtBank, + AwaitingCheckoutAuthorisation, +} + +impl From for api::IncomingWebhookEvent { + fn from(status: VoltWebhookBodyEventType) -> Self { + match status { + VoltWebhookBodyEventType::Payment(payment_data) => match payment_data.status { + VoltWebhookPaymentStatus::Completed | VoltWebhookPaymentStatus::Received => { + Self::PaymentIntentSuccess + } + VoltWebhookPaymentStatus::Failed | VoltWebhookPaymentStatus::NotReceived => { + Self::PaymentIntentFailure + } + VoltWebhookPaymentStatus::Pending => Self::PaymentIntentProcessing, + }, + VoltWebhookBodyEventType::Refund(refund_data) => match refund_data.status { + VoltWebhookRefundsStatus::RefundConfirmed => Self::RefundSuccess, + VoltWebhookRefundsStatus::RefundFailed => Self::RefundFailure, + }, + } + } +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct VoltErrorResponse { pub exception: VoltErrorException, } +#[derive(Debug, Deserialize)] +pub struct VoltAuthErrorResponse { + pub code: u64, + pub message: String, +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct VoltErrorException { @@ -423,3 +617,32 @@ pub struct VoltErrorList { pub property: String, pub message: String, } + +fn is_payment_failure(status: enums::AttemptStatus) -> bool { + match status { + common_enums::AttemptStatus::AuthenticationFailed + | common_enums::AttemptStatus::AuthorizationFailed + | common_enums::AttemptStatus::CaptureFailed + | common_enums::AttemptStatus::VoidFailed + | common_enums::AttemptStatus::Failure => true, + common_enums::AttemptStatus::Started + | common_enums::AttemptStatus::RouterDeclined + | common_enums::AttemptStatus::AuthenticationPending + | common_enums::AttemptStatus::AuthenticationSuccessful + | common_enums::AttemptStatus::Authorized + | common_enums::AttemptStatus::Charged + | common_enums::AttemptStatus::Authorizing + | common_enums::AttemptStatus::CodInitiated + | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidInitiated + | common_enums::AttemptStatus::CaptureInitiated + | common_enums::AttemptStatus::AutoRefunded + | common_enums::AttemptStatus::PartialCharged + | common_enums::AttemptStatus::PartialChargedAndChargeable + | common_enums::AttemptStatus::Unresolved + | common_enums::AttemptStatus::Pending + | common_enums::AttemptStatus::PaymentMethodAwaited + | common_enums::AttemptStatus::ConfirmationAwaited + | common_enums::AttemptStatus::DeviceDataCollectionPending => false, + } +} diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index 4fa66f0d8e5..d2ec08c607c 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -1,6 +1,8 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "payouts")] +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "payouts")] use masking::PeekInterface; @@ -24,7 +26,7 @@ use crate::{ utils::BytesExt, }; #[cfg(feature = "payouts")] -use crate::{core::payments, routes, utils}; +use crate::{core::payments, routes}; #[derive(Debug, Clone)] pub struct Wise; @@ -88,7 +90,7 @@ impl ConnectorCommon for Wise { let default_status = response.status.unwrap_or_default().to_string(); match response.errors { Some(errs) => { - if let Some(e) = errs.get(0) { + if let Some(e) = errs.first() { Ok(types::ErrorResponse { status_code: res.status_code, code: e.code.clone(), @@ -155,6 +157,20 @@ impl types::PaymentsResponseData, > for Wise { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Wise".to_string()) + .into(), + ) + } } impl api::PaymentSession for Wise {} @@ -285,7 +301,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutQuoteRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -351,7 +362,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WiseRecipientCreateRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -431,7 +437,7 @@ impl .headers(types::PayoutRecipientType::get_headers( self, req, connectors, )?) - .body(types::PayoutRecipientType::get_request_body( + .set_body(types::PayoutRecipientType::get_request_body( self, req, connectors, )?) .build(); @@ -527,14 +533,9 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutCreateRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -547,7 +548,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutFulfillRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -648,7 +644,7 @@ impl services::ConnectorIntegration; #[cfg(feature = "payouts")] use crate::{ - connector::utils::RouterData, + connector::utils::{self, RouterData}, types::{ api::payouts, storage::enums::{self as storage_enums, PayoutEntityType}, @@ -344,10 +344,9 @@ fn get_payout_bank_details( bic: b.bic, ..WiseBankDetails::default() }), - _ => Err(errors::ConnectorError::NotSupported { - message: "Card payout creation is not supported".to_string(), - connector: "Wise", - }), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ))?, } } @@ -371,10 +370,9 @@ impl TryFrom<&types::PayoutsRouterData> for WiseRecipientCreateRequest { }), }?; match request.payout_type.to_owned() { - storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotSupported { - message: "Card payout creation is not supported".to_string(), - connector: "Wise", - })?, + storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ))?, storage_enums::PayoutType::Bank => { let account_holder_name = customer_details .ok_or(errors::ConnectorError::MissingRequiredField { @@ -432,10 +430,9 @@ impl TryFrom<&types::PayoutsRouterData> for WisePayoutQuoteRequest { target_currency: request.destination_currency.to_string(), pay_out: WisePayOutOption::default(), }), - storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotSupported { - message: "Card payout fulfillment is not supported".to_string(), - connector: "Wise", - })?, + storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ))?, } } } @@ -489,10 +486,9 @@ impl TryFrom<&types::PayoutsRouterData> for WisePayoutCreateRequest { details: wise_transfer_details, }) } - storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotSupported { - message: "Card payout fulfillment is not supported".to_string(), - connector: "Wise", - })?, + storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ))?, } } } @@ -533,10 +529,9 @@ impl TryFrom<&types::PayoutsRouterData> for WisePayoutFulfillRequest { storage_enums::PayoutType::Bank => Ok(Self { fund_type: FundType::default(), }), - storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotSupported { - message: "Card payout fulfillment is not supported".to_string(), - connector: "Wise", - })?, + storage_enums::PayoutType::Card => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ))?, } } } @@ -599,10 +594,9 @@ impl TryFrom for RecipientType { PayoutMethodData::Bank(api_models::payouts::Bank::Ach(_)) => Ok(Self::Aba), PayoutMethodData::Bank(api_models::payouts::Bank::Bacs(_)) => Ok(Self::SortCode), PayoutMethodData::Bank(api_models::payouts::Bank::Sepa(_)) => Ok(Self::Iban), - _ => Err(errors::ConnectorError::NotSupported { - message: "Requested payout_method_type is not supported".to_string(), - connector: "Wise", - } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Wise"), + ) .into()), } } diff --git a/crates/router/src/connector/worldline.rs b/crates/router/src/connector/worldline.rs index 3d928624df8..a1ca8a110bc 100644 --- a/crates/router/src/connector/worldline.rs +++ b/crates/router/src/connector/worldline.rs @@ -3,7 +3,7 @@ pub mod transformers; use std::fmt::Debug; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -11,10 +11,9 @@ use ring::hmac; use time::{format_description, OffsetDateTime}; use transformers as worldline; -use super::utils::RefundsRequestData; +use super::utils::{self as connector_utils, RefundsRequestData}; use crate::{ configs::settings::Connectors, - connector::{utils as connector_utils, utils as conn_utils}, consts, core::errors::{self, CustomResult}, headers, logger, @@ -28,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, crypto, BytesExt, OptionExt}, + utils::{crypto, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -174,6 +173,20 @@ impl types::PaymentsResponseData, > for Worldline { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Worldline".to_string()) + .into(), + ) + } } impl api::PaymentToken for Worldline {} @@ -379,15 +392,10 @@ impl ConnectorIntegration, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = worldline::ApproveRequest::try_from(req)?; - let worldline_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldline_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -407,7 +415,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = worldline::WorldlineRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -501,12 +509,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldline_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -528,7 +531,7 @@ impl ConnectorIntegration, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = worldline::WorldlineRefundRequest::try_from(req)?; - let refund_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(refund_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -617,7 +615,7 @@ impl ConnectorIntegration, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let header_value = conn_utils::get_header_key_value("X-GCS-Signature", request.headers)?; + let header_value = + connector_utils::get_header_key_value("X-GCS-Signature", request.headers)?; let signature = consts::BASE64_ENGINE .decode(header_value.as_bytes()) .into_report() diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs index b5739fe857a..c55663d59f4 100644 --- a/crates/router/src/connector/worldline/transformers.rs +++ b/crates/router/src/connector/worldline/transformers.rs @@ -342,16 +342,21 @@ fn make_card_request( req: &PaymentsAuthorizeData, ccard: &payments::Card, ) -> Result> { - let expiry_year = ccard.card_exp_year.peek().clone(); + let expiry_year = ccard.card_exp_year.peek(); let secret_value = format!( "{}{}", ccard.card_exp_month.peek(), - &expiry_year[expiry_year.len() - 2..] + &expiry_year + .get(expiry_year.len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? ); let expiry_date: Secret = Secret::new(secret_value); let card = Card { card_number: ccard.card_number.clone(), - cardholder_name: ccard.card_holder_name.clone(), + cardholder_name: ccard + .card_holder_name + .clone() + .unwrap_or(Secret::new("".to_string())), cvv: ccard.card_cvc.clone(), expiry_date, }; diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index bfa58a74f33..d4ac530172d 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -4,7 +4,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as worldpay; @@ -26,7 +26,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self as ext_traits, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -125,6 +125,20 @@ impl types::PaymentsResponseData, > for Worldpay { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Worldpay".to_string()) + .into(), + ) + } } impl api::PaymentToken for Worldpay {} @@ -277,9 +291,6 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = worldpay::WorldpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = WorldpayPaymentsRequest::try_from(&connector_router_data)?; - let worldpay_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - ext_traits::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldpay_payment_request)) + let connector_req = WorldpayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -470,7 +476,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = WorldpayRefundRequest::try_from(req)?; - let fiserv_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - ext_traits::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_refund_request)) + ) -> CustomResult { + let connector_req = WorldpayRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn get_url( @@ -560,7 +561,7 @@ impl ConnectorIntegration for Zen { + fn build_request( + &self, + _req: &types::RouterData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Zen".to_string()).into()) + } } impl ConnectorIntegration @@ -218,20 +229,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = zen::ZenPaymentsRequest::try_from(&connector_router_data)?; - let zen_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(zen_req)) + let connector_req = zen::ZenPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -249,7 +255,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = zen::ZenRefundRequest::try_from(&connector_router_data)?; - let zen_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(zen_req)) + let connector_req = zen::ZenRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -444,7 +445,7 @@ impl ConnectorIntegration, &Card)> for Z card: Some(ZenCardDetails { number: ccard.card_number.clone(), expiry_date: ccard - .get_card_expiry_month_year_2_digit_with_delimiter("".to_owned()), + .get_card_expiry_month_year_2_digit_with_delimiter("".to_owned())?, cvv: ccard.card_cvc.clone(), }), descriptor: item @@ -538,7 +538,7 @@ fn get_checkout_signature( .pay_wall_secret .clone() .ok_or(errors::ConnectorError::RequestEncodingFailed)?; - let mut signature_data = get_signature_data(checkout_request); + let mut signature_data = get_signature_data(checkout_request)?; signature_data.push_str(&pay_wall_secret); let payload_digest = digest::digest(&digest::SHA256, signature_data.as_bytes()); let mut signature = hex::encode(payload_digest); @@ -547,7 +547,9 @@ fn get_checkout_signature( } /// Fields should be in alphabetical order -fn get_signature_data(checkout_request: &CheckoutRequest) -> String { +fn get_signature_data( + checkout_request: &CheckoutRequest, +) -> Result { let specified_payment_channel = match checkout_request.specified_payment_channel { ZenPaymentChannels::PclCard => "pcl_card", ZenPaymentChannels::PclGooglepay => "pcl_googlepay", @@ -568,21 +570,19 @@ fn get_signature_data(checkout_request: &CheckoutRequest) -> String { ]; for index in 0..checkout_request.items.len() { let prefix = format!("items[{index}]."); + let checkout_request_items = checkout_request + .items + .get(index) + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; signature_data.push(format!( "{prefix}lineamounttotal={}", - checkout_request.items[index].line_amount_total - )); - signature_data.push(format!( - "{prefix}name={}", - checkout_request.items[index].name - )); - signature_data.push(format!( - "{prefix}price={}", - checkout_request.items[index].price + checkout_request_items.line_amount_total )); + signature_data.push(format!("{prefix}name={}", checkout_request_items.name)); + signature_data.push(format!("{prefix}price={}", checkout_request_items.price)); signature_data.push(format!( "{prefix}quantity={}", - checkout_request.items[index].quantity + checkout_request_items.quantity )); } signature_data.push(format!( @@ -598,7 +598,7 @@ fn get_signature_data(checkout_request: &CheckoutRequest) -> String { )); signature_data.push(format!("urlredirect={}", checkout_request.url_redirect)); let signature = signature_data.join("&"); - signature.to_lowercase() + Ok(signature.to_lowercase()) } fn get_customer( diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 4a2d2831d10..387da3c0641 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -24,6 +24,12 @@ pub const REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC: &str = ///Payment intent fulfillment default timeout (in seconds) pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60; +/// Payment intent default client secret expiry (in seconds) +pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60; + +/// The length of a merchant fingerprint secret +pub const FINGERPRINT_SECRET_LENGTH: usize = 64; + // String literals pub(crate) const NO_ERROR_MESSAGE: &str = "No error message"; pub(crate) const NO_ERROR_CODE: &str = "No error code"; @@ -70,3 +76,14 @@ pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify"; #[cfg(feature = "olap")] pub const VERIFY_CONNECTOR_MERCHANT_ID: &str = "test_merchant"; + +#[cfg(feature = "olap")] +pub const CONNECTOR_ONBOARDING_CONFIG_PREFIX: &str = "onboarding"; + +/// Max payment session expiry +pub const MAX_SESSION_EXPIRY: u32 = 7890000; + +/// Min payment session expiry +pub const MIN_SESSION_EXPIRY: u32 = 60; + +pub const LOCKER_HEALTH_CALL_PATH: &str = "/health"; diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index 0bd197ee22e..5ae4b0be33d 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -1,6 +1,7 @@ pub mod admin; pub mod api_keys; pub mod api_locking; +pub mod blocklist; pub mod cache; pub mod cards_info; pub mod conditional_config; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 113bc7d677d..fd4cae3a2b9 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -10,6 +10,7 @@ use common_utils::{ ext_traits::{AsyncExt, ConfigExt, Encode, ValueExt}, pii, }; +use diesel_models::configs; use error_stack::{report, FutureExt, IntoReport, ResultExt}; use futures::future::try_join_all; use masking::{PeekInterface, Secret}; @@ -141,16 +142,16 @@ pub async fn create_merchant_account( .transpose()? .map(Secret::new); - let payment_link_config = req - .payment_link_config - .as_ref() - .map(|pl_metadata| { - utils::Encode::::encode_to_value(pl_metadata) - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_link_config", - }) + let fingerprint = Some(utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs")); + if let Some(fingerprint) = fingerprint { + db.insert_config(configs::ConfigNew { + key: format!("fingerprint_secret_{}", req.merchant_id), + config: fingerprint, }) - .transpose()?; + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Mot able to generate Merchant fingerprint")?; + }; let organization_id = if let Some(organization_id) = req.organization_id.as_ref() { db.find_organization_by_org_id(organization_id) @@ -200,15 +201,15 @@ pub async fn create_merchant_account( primary_business_details, created_at: date_time::now(), modified_at: date_time::now(), + intent_fulfillment_time: None, frm_routing_algorithm: req.frm_routing_algorithm, - intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), payout_routing_algorithm: req.payout_routing_algorithm, id: None, organization_id, is_recon_enabled: false, default_profile: None, recon_status: diesel_models::enums::ReconStatus::NotRequested, - payment_link_config, + payment_link_config: None, }) } .await @@ -430,6 +431,8 @@ pub async fn update_business_profile_cascade( frm_routing_algorithm: None, payout_routing_algorithm: None, applepay_verified_domains: None, + payment_link_config: None, + session_expiry: None, }; let update_futures = business_profiles.iter().map(|business_profile| async { @@ -582,10 +585,10 @@ pub async fn merchant_account_update( publishable_key: None, primary_business_details, frm_routing_algorithm: req.frm_routing_algorithm, - intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), + intent_fulfillment_time: None, payout_routing_algorithm: req.payout_routing_algorithm, default_profile: business_profile_id_update, - payment_link_config: req.payment_link_config, + payment_link_config: None, }; let response = db @@ -777,14 +780,13 @@ pub async fn create_payment_connector( let pm_auth_connector = api_enums::convert_pm_auth_connector(req.connector_name.to_string().as_str()); - let is_unroutable_connector = if pm_auth_connector.is_some() { + if pm_auth_connector.is_some() { if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth { return Err(errors::ApiErrorResponse::InvalidRequestData { message: "Invalid connector type given".to_string(), }) .into_report(); } - true } else { let routable_connector_option = req .connector_name @@ -795,7 +797,6 @@ pub async fn create_payment_connector( message: "Invalid connector name given".to_string(), })?; routable_connector = Some(routable_connector_option); - false }; // If connector label is not passed in the request, generate one @@ -864,31 +865,13 @@ pub async fn create_payment_connector( // The purpose of this merchant account update is just to update the // merchant account `modified_at` field for KGraph cache invalidation - let merchant_account_update = storage::MerchantAccountUpdate::Update { - merchant_name: None, - merchant_details: None, - return_url: None, - webhook_details: None, - sub_merchants_enabled: None, - parent_merchant_id: None, - enable_payment_response_hash: None, - locker_id: None, - payment_response_hash_key: None, - primary_business_details: None, - metadata: None, - publishable_key: None, - redirect_to_merchant_with_http_post: None, - routing_algorithm: None, - intent_fulfillment_time: None, - frm_routing_algorithm: None, - payout_routing_algorithm: None, - default_profile: None, - payment_link_config: None, - }; - state .store - .update_specific_fields_in_merchant(merchant_id, merchant_account_update, &key_store) + .update_specific_fields_in_merchant( + merchant_id, + storage::MerchantAccountUpdate::ModifiedAtUpdate, + &key_store, + ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("error updating the merchant account when creating payment connector")?; @@ -1019,40 +1002,6 @@ pub async fn create_payment_connector( ], ); - if !is_unroutable_connector { - if let Some(routable_connector_val) = routable_connector { - let choice = routing_types::RoutableConnectorChoice { - #[cfg(feature = "backwards_compatibility")] - choice_kind: routing_types::RoutableChoiceKind::FullStruct, - connector: routable_connector_val, - #[cfg(feature = "connector_choice_mca_id")] - merchant_connector_id: Some(mca.merchant_connector_id.clone()), - #[cfg(not(feature = "connector_choice_mca_id"))] - sub_label: req.business_sub_label.clone(), - }; - - if !default_routing_config.contains(&choice) { - default_routing_config.push(choice.clone()); - routing_helpers::update_merchant_default_config( - &*state.clone().store, - merchant_id, - default_routing_config, - ) - .await?; - } - - if !default_routing_config_for_profile.contains(&choice) { - default_routing_config_for_profile.push(choice); - routing_helpers::update_merchant_default_config( - &*state.store, - &profile_id, - default_routing_config_for_profile, - ) - .await?; - } - } - }; - let mca_response = mca.try_into()?; Ok(service_api::ApplicationResponse::Json(mca_response)) } @@ -1223,6 +1172,34 @@ pub async fn update_payment_connector( field_name: "connector_account_details".to_string(), expected_format: "auth_type and api_key".to_string(), })?; + let connector_name = mca.connector_name.as_ref(); + let connector_enum = api_models::enums::Connector::from_str(connector_name) + .into_report() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector", + }) + .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?; + validate_auth_and_metadata_type(connector_enum, &auth, &req.metadata).map_err( + |err| match *err.current_context() { + errors::ConnectorError::InvalidConnectorName => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The connector name is invalid".to_string(), + }) + } + errors::ConnectorError::InvalidConnectorConfig { config: field_name } => err + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: format!("The {} is invalid", field_name), + }), + errors::ConnectorError::FailedToObtainAuthType => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The auth type is invalid for the connector".to_string(), + }) + } + _ => err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The request body is invalid".to_string(), + }), + }, + )?; let (connector_status, disabled) = validate_status_and_disabled(req.status, req.disabled, auth, mca.status)?; @@ -1241,6 +1218,17 @@ pub async fn update_payment_connector( } } + // The purpose of this merchant account update is just to update the + // merchant account `modified_at` field for KGraph cache invalidation + db.update_specific_fields_in_merchant( + merchant_id, + storage::MerchantAccountUpdate::ModifiedAtUpdate, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error updating the merchant account when updating payment connector")?; + let payment_connector = storage::MerchantConnectorAccountUpdate::Update { merchant_id: None, connector_type: Some(req.connector_type), @@ -1470,6 +1458,9 @@ pub async fn create_business_profile( request: api::BusinessProfileCreate, merchant_id: &str, ) -> RouterResponse { + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; + } let db = state.store.as_ref(); let key_store = db .get_merchant_key_store_by_merchant_id(merchant_id, &db.get_master_key().to_vec().into()) @@ -1583,6 +1574,10 @@ pub async fn update_business_profile( })? } + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; + } + let webhook_details = request .webhook_details .as_ref() @@ -1605,6 +1600,17 @@ pub async fn update_business_profile( .attach_printable("Invalid routing algorithm given")?; } + let payment_link_config = request + .payment_link_config + .as_ref() + .map(|pl_metadata| { + utils::Encode::::encode_to_value(pl_metadata) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", + }) + }) + .transpose()?; + let business_profile_update = storage::business_profile::BusinessProfileUpdateInternal { profile_name: request.profile_name, modified_at: Some(date_time::now()), @@ -1620,6 +1626,8 @@ pub async fn update_business_profile( payout_routing_algorithm: request.payout_routing_algorithm, is_recon_enabled: None, applepay_verified_domains: request.applepay_verified_domains, + payment_link_config, + session_expiry: request.session_expiry.map(i64::from), }; let updated_business_profile = db @@ -1795,6 +1803,10 @@ pub(crate) fn validate_auth_and_metadata_type( payu::transformers::PayuAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Placetopay => { + placetopay::transformers::PlacetopayAuthType::try_from(val)?; + Ok(()) + } api_enums::Connector::Powertranz => { powertranz::transformers::PowertranzAuthType::try_from(val)?; Ok(()) @@ -1855,6 +1867,10 @@ pub(crate) fn validate_auth_and_metadata_type( signifyd::transformers::SignifydAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Riskified => { + riskified::transformers::RiskifiedAuthType::try_from(val)?; + Ok(()) + } api_enums::Connector::Plaid => { PlaidAuthType::foreign_try_from(val)?; Ok(()) diff --git a/crates/router/src/core/blocklist.rs b/crates/router/src/core/blocklist.rs new file mode 100644 index 00000000000..85845602449 --- /dev/null +++ b/crates/router/src/core/blocklist.rs @@ -0,0 +1,41 @@ +pub mod transformers; +pub mod utils; + +use api_models::blocklist as api_blocklist; + +use crate::{ + core::errors::{self, RouterResponse}, + routes::AppState, + services, + types::domain, +}; + +pub async fn add_entry_to_blocklist( + state: AppState, + merchant_account: domain::MerchantAccount, + body: api_blocklist::AddToBlocklistRequest, +) -> RouterResponse { + utils::insert_entry_into_blocklist(&state, merchant_account.merchant_id, body) + .await + .map(services::ApplicationResponse::Json) +} + +pub async fn remove_entry_from_blocklist( + state: AppState, + merchant_account: domain::MerchantAccount, + body: api_blocklist::DeleteFromBlocklistRequest, +) -> RouterResponse { + utils::delete_entry_from_blocklist(&state, merchant_account.merchant_id, body) + .await + .map(services::ApplicationResponse::Json) +} + +pub async fn list_blocklist_entries( + state: AppState, + merchant_account: domain::MerchantAccount, + query: api_blocklist::ListBlocklistQuery, +) -> RouterResponse> { + utils::list_blocklist_entries_for_merchant(&state, merchant_account.merchant_id, query) + .await + .map(services::ApplicationResponse::Json) +} diff --git a/crates/router/src/core/blocklist/transformers.rs b/crates/router/src/core/blocklist/transformers.rs new file mode 100644 index 00000000000..2cb5f86a264 --- /dev/null +++ b/crates/router/src/core/blocklist/transformers.rs @@ -0,0 +1,13 @@ +use api_models::blocklist; + +use crate::types::{storage, transformers::ForeignFrom}; + +impl ForeignFrom for blocklist::AddToBlocklistResponse { + fn foreign_from(from: storage::Blocklist) -> Self { + Self { + fingerprint_id: from.fingerprint_id, + data_kind: from.data_kind, + created_at: from.created_at, + } + } +} diff --git a/crates/router/src/core/blocklist/utils.rs b/crates/router/src/core/blocklist/utils.rs new file mode 100644 index 00000000000..b7effaf63ac --- /dev/null +++ b/crates/router/src/core/blocklist/utils.rs @@ -0,0 +1,359 @@ +use api_models::blocklist as api_blocklist; +use common_utils::crypto::{self, SignMessage}; +use error_stack::{IntoReport, ResultExt}; +#[cfg(feature = "kms")] +use external_services::kms; + +use super::{errors, AppState}; +use crate::{ + consts, + core::errors::{RouterResult, StorageErrorExt}, + types::{storage, transformers::ForeignInto}, + utils, +}; + +pub async fn delete_entry_from_blocklist( + state: &AppState, + merchant_id: String, + request: api_blocklist::DeleteFromBlocklistRequest, +) -> RouterResult { + let blocklist_entry = match request { + api_blocklist::DeleteFromBlocklistRequest::CardBin(bin) => { + delete_card_bin_blocklist_entry(state, &bin, &merchant_id).await? + } + + api_blocklist::DeleteFromBlocklistRequest::ExtendedCardBin(xbin) => { + delete_card_bin_blocklist_entry(state, &xbin, &merchant_id).await? + } + + api_blocklist::DeleteFromBlocklistRequest::Fingerprint(fingerprint_id) => { + let blocklist_fingerprint = state + .store + .find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &merchant_id, + &fingerprint_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "blocklist record with given fingerprint id not found".to_string(), + })?; + + #[cfg(feature = "kms")] + let decrypted_fingerprint = kms::get_kms_client(&state.conf.kms) + .await + .decrypt(blocklist_fingerprint.encrypted_fingerprint) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to kms decrypt fingerprint")?; + + #[cfg(not(feature = "kms"))] + let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint; + + let blocklist_entry = state + .store + .delete_blocklist_entry_by_merchant_id_fingerprint_id(&merchant_id, &fingerprint_id) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "no blocklist record for the given fingerprint id was found" + .to_string(), + })?; + + state + .store + .delete_blocklist_lookup_entry_by_merchant_id_fingerprint( + &merchant_id, + &decrypted_fingerprint, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "no blocklist record for the given fingerprint id was found" + .to_string(), + })?; + + blocklist_entry + } + }; + + Ok(blocklist_entry.foreign_into()) +} + +pub async fn list_blocklist_entries_for_merchant( + state: &AppState, + merchant_id: String, + query: api_blocklist::ListBlocklistQuery, +) -> RouterResult> { + state + .store + .list_blocklist_entries_by_merchant_id_data_kind( + &merchant_id, + query.data_kind, + query.limit.into(), + query.offset.into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "no blocklist records found".to_string(), + }) + .map(|v| v.into_iter().map(ForeignInto::foreign_into).collect()) +} + +fn validate_card_bin(bin: &str) -> RouterResult<()> { + if bin.len() == 6 && bin.chars().all(|c| c.is_ascii_digit()) { + Ok(()) + } else { + Err(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "data".to_string(), + expected_format: "a 6 digit number".to_string(), + }) + .into_report() + } +} + +fn validate_extended_card_bin(bin: &str) -> RouterResult<()> { + if bin.len() == 8 && bin.chars().all(|c| c.is_ascii_digit()) { + Ok(()) + } else { + Err(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "data".to_string(), + expected_format: "an 8 digit number".to_string(), + }) + .into_report() + } +} + +pub async fn insert_entry_into_blocklist( + state: &AppState, + merchant_id: String, + to_block: api_blocklist::AddToBlocklistRequest, +) -> RouterResult { + let blocklist_entry = match &to_block { + api_blocklist::AddToBlocklistRequest::CardBin(bin) => { + validate_card_bin(bin)?; + duplicate_check_insert_bin( + bin, + state, + &merchant_id, + common_enums::BlocklistDataKind::CardBin, + ) + .await? + } + + api_blocklist::AddToBlocklistRequest::ExtendedCardBin(bin) => { + validate_extended_card_bin(bin)?; + duplicate_check_insert_bin( + bin, + state, + &merchant_id, + common_enums::BlocklistDataKind::ExtendedCardBin, + ) + .await? + } + + api_blocklist::AddToBlocklistRequest::Fingerprint(fingerprint_id) => { + let blocklist_entry_result = state + .store + .find_blocklist_entry_by_merchant_id_fingerprint_id(&merchant_id, fingerprint_id) + .await; + + match blocklist_entry_result { + Ok(_) => { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: "data associated with the given fingerprint is already blocked" + .to_string(), + }) + .into_report(); + } + + // if it is a db not found error, we can proceed as normal + Err(inner) if inner.current_context().is_db_not_found() => {} + + err @ Err(_) => { + err.change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error fetching blocklist entry from table")?; + } + } + + let blocklist_fingerprint = state + .store + .find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &merchant_id, + fingerprint_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "fingerprint not found".to_string(), + })?; + + #[cfg(feature = "kms")] + let decrypted_fingerprint = kms::get_kms_client(&state.conf.kms) + .await + .decrypt(blocklist_fingerprint.encrypted_fingerprint) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to kms decrypt encrypted fingerprint")?; + + #[cfg(not(feature = "kms"))] + let decrypted_fingerprint = blocklist_fingerprint.encrypted_fingerprint; + + state + .store + .insert_blocklist_lookup_entry( + diesel_models::blocklist_lookup::BlocklistLookupNew { + merchant_id: merchant_id.clone(), + fingerprint: decrypted_fingerprint, + }, + ) + .await + .to_duplicate_response(errors::ApiErrorResponse::PreconditionFailed { + message: "the payment instrument associated with the given fingerprint is already in the blocklist".to_string(), + }) + .attach_printable("failed to add fingerprint to blocklist lookup")?; + + state + .store + .insert_blocklist_entry(storage::BlocklistNew { + merchant_id: merchant_id.clone(), + fingerprint_id: fingerprint_id.clone(), + data_kind: blocklist_fingerprint.data_kind, + metadata: None, + created_at: common_utils::date_time::now(), + }) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to add fingerprint to pm blocklist")? + } + }; + + Ok(blocklist_entry.foreign_into()) +} + +pub async fn get_merchant_fingerprint_secret( + state: &AppState, + merchant_id: &str, +) -> RouterResult { + let key = get_merchant_fingerprint_secret_key(merchant_id); + let config_fetch_result = state.store.find_config_by_key(&key).await; + + match config_fetch_result { + Ok(config) => Ok(config.config), + + Err(e) if e.current_context().is_db_not_found() => { + let new_fingerprint_secret = + utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs"); + let new_config = storage::ConfigNew { + key, + config: new_fingerprint_secret.clone(), + }; + + state + .store + .insert_config(new_config) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to create new fingerprint secret for merchant")?; + + Ok(new_fingerprint_secret) + } + + Err(e) => Err(e) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error fetching merchant fingerprint secret"), + } +} + +pub fn get_merchant_fingerprint_secret_key(merchant_id: &str) -> String { + format!("fingerprint_secret_{merchant_id}") +} + +async fn duplicate_check_insert_bin( + bin: &str, + state: &AppState, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, +) -> RouterResult { + let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?; + let bin_fingerprint = crypto::HmacSha512::sign_message( + &crypto::HmacSha512, + merchant_secret.clone().as_bytes(), + bin.as_bytes(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error in bin hash creation")?; + + let encoded_fingerprint = hex::encode(bin_fingerprint.clone()); + + let blocklist_entry_result = state + .store + .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin) + .await; + + match blocklist_entry_result { + Ok(_) => { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: "provided bin is already blocked".to_string(), + }) + .into_report(); + } + + Err(e) if e.current_context().is_db_not_found() => {} + + err @ Err(_) => { + return err + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to fetch blocklist entry"); + } + } + + // Checking for duplicacy + state + .store + .insert_blocklist_lookup_entry(diesel_models::blocklist_lookup::BlocklistLookupNew { + merchant_id: merchant_id.to_string(), + fingerprint: encoded_fingerprint.clone(), + }) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error inserting blocklist lookup entry")?; + + state + .store + .insert_blocklist_entry(storage::BlocklistNew { + merchant_id: merchant_id.to_string(), + fingerprint_id: bin.to_string(), + data_kind, + metadata: None, + created_at: common_utils::date_time::now(), + }) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error inserting pm blocklist item") +} + +async fn delete_card_bin_blocklist_entry( + state: &AppState, + bin: &str, + merchant_id: &str, +) -> RouterResult { + let merchant_secret = get_merchant_fingerprint_secret(state, merchant_id).await?; + let bin_fingerprint = crypto::HmacSha512 + .sign_message(merchant_secret.as_bytes(), bin.as_bytes()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error when hashing card bin")?; + let encoded_fingerprint = hex::encode(bin_fingerprint); + + state + .store + .delete_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, &encoded_fingerprint) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "could not find a blocklist entry for the given bin".to_string(), + })?; + + state + .store + .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "could not find a blocklist entry for the given bin".to_string(), + }) +} diff --git a/crates/router/src/core/connector_onboarding.rs b/crates/router/src/core/connector_onboarding.rs index e48026edc2d..e6c1fc9d378 100644 --- a/crates/router/src/core/connector_onboarding.rs +++ b/crates/router/src/core/connector_onboarding.rs @@ -1,5 +1,4 @@ use api_models::{connector_onboarding as api, enums}; -use error_stack::ResultExt; use masking::Secret; use crate::{ @@ -19,16 +18,23 @@ pub trait AccessToken { pub async fn get_action_url( state: AppState, + user_from_token: auth::UserFromToken, request: api::ActionUrlRequest, ) -> RouterResponse { + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + let tracking_id = + utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector) + .await?; match (is_enabled, request.connector) { (Some(true), enums::Connector::Paypal) => { let action_url = Box::pin(paypal::get_action_url_from_paypal( state, - request.connector_id, + tracking_id, request.return_url, )) .await?; @@ -49,40 +55,42 @@ pub async fn sync_onboarding_status( user_from_token: auth::UserFromToken, request: api::OnboardingSyncRequest, ) -> RouterResponse { - let merchant_account = user_from_token - .get_merchant_account(state.clone()) - .await - .change_context(ApiErrorResponse::MerchantAccountNotFound)?; + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + let tracking_id = + utils::get_tracking_id_from_configs(&state, &request.connector_id, request.connector) + .await?; match (is_enabled, request.connector) { (Some(true), enums::Connector::Paypal) => { let status = Box::pin(paypal::sync_merchant_onboarding_status( state.clone(), - request.connector_id.clone(), + tracking_id, )) .await?; if let api::OnboardingStatus::PayPal(api::PayPalOnboardingStatus::Success( - ref inner_data, + ref paypal_onboarding_data, )) = status { let connector_onboarding_conf = state.conf.connector_onboarding.clone(); let auth_details = oss_types::ConnectorAuthType::SignatureKey { api_key: connector_onboarding_conf.paypal.client_secret, key1: connector_onboarding_conf.paypal.client_id, - api_secret: Secret::new(inner_data.payer_id.clone()), + api_secret: Secret::new(paypal_onboarding_data.payer_id.clone()), }; - let some_data = paypal::update_mca( + let update_mca_data = paypal::update_mca( &state, - &merchant_account, + user_from_token.merchant_id, request.connector_id.to_owned(), auth_details, ) .await?; return Ok(ApplicationResponse::Json(api::OnboardingStatus::PayPal( - api::PayPalOnboardingStatus::ConnectorIntegrated(some_data), + api::PayPalOnboardingStatus::ConnectorIntegrated(update_mca_data), ))); } Ok(ApplicationResponse::Json(status)) @@ -94,3 +102,15 @@ pub async fn sync_onboarding_status( .into()), } } + +pub async fn reset_tracking_id( + state: AppState, + user_from_token: auth::UserFromToken, + request: api::ResetTrackingIdRequest, +) -> RouterResponse<()> { + utils::check_if_connector_exists(&state, &request.connector_id, &user_from_token.merchant_id) + .await?; + utils::set_tracking_id_in_configs(&state, &request.connector_id, request.connector).await?; + + Ok(ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs index 30aa69067b5..f18681f8cfd 100644 --- a/crates/router/src/core/connector_onboarding/paypal.rs +++ b/crates/router/src/core/connector_onboarding/paypal.rs @@ -23,11 +23,11 @@ fn build_referral_url(state: AppState) -> String { async fn build_referral_request( state: AppState, - connector_id: String, + tracking_id: String, return_url: String, ) -> RouterResult { let access_token = utils::paypal::generate_access_token(state.clone()).await?; - let request_body = types::paypal::PartnerReferralRequest::new(connector_id, return_url); + let request_body = types::paypal::PartnerReferralRequest::new(tracking_id, return_url); utils::paypal::build_paypal_post_request( build_referral_url(state), @@ -38,12 +38,12 @@ async fn build_referral_request( pub async fn get_action_url_from_paypal( state: AppState, - connector_id: String, + tracking_id: String, return_url: String, ) -> RouterResult { let referral_request = Box::pin(build_referral_request( state.clone(), - connector_id, + tracking_id, return_url, )) .await?; @@ -137,7 +137,7 @@ async fn find_paypal_merchant_by_tracking_id( pub async fn update_mca( state: &AppState, - merchant_account: &oss_types::domain::MerchantAccount, + merchant_id: String, connector_id: String, auth_details: oss_types::ConnectorAuthType, ) -> RouterResult { @@ -159,13 +159,9 @@ pub async fn update_mca( connector_webhook_details: None, pm_auth_config: None, }; - let mca_response = admin::update_payment_connector( - state.clone(), - &merchant_account.merchant_id, - &connector_id, - request, - ) - .await?; + let mca_response = + admin::update_payment_connector(state.clone(), &merchant_id, &connector_id, request) + .await?; match mca_response { ApplicationResponse::Json(mca_data) => Ok(mca_data), diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 054f4053504..cbc4290f63b 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -226,7 +226,7 @@ pub enum KmsError { Utf8DecodingFailed, } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, serde::Serialize)] pub enum WebhooksFlowError { #[error("Merchant webhook config not found")] MerchantConfigNotFound, diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs index d34cbf88aaa..54ec4ec1e29 100644 --- a/crates/router/src/core/errors/api_error_response.rs +++ b/crates/router/src/core/errors/api_error_response.rs @@ -60,7 +60,7 @@ pub enum ApiErrorResponse { CustomerRedacted, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")] MaximumRefundCount, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "Refund amount exceeds the payment amount")] + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")] RefundAmountExceedsPaymentAmount, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")] PaymentUnexpectedState { @@ -186,6 +186,8 @@ pub enum ApiErrorResponse { PaymentNotSucceeded, #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")] MerchantConnectorAccountDisabled, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified payment is blocked")] + PaymentBlocked, #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")] SuccessfulPaymentNotFound, #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")] @@ -238,6 +240,8 @@ pub enum ApiErrorResponse { CurrencyNotSupported { message: String }, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] InvalidConnectorConfiguration { config: String }, + #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] + CurrencyConversionFailed, } impl PTError for ApiErrorResponse { diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index 17aa6f3a207..ff764cafed6 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -65,7 +65,7 @@ impl ErrorSwitch for ApiErrorRespon } Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)), Self::RefundAmountExceedsPaymentAmount => { - AER::BadRequest(ApiError::new("IR", 13, "Refund amount exceeds the payment amount", None)) + AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None)) } Self::PaymentUnexpectedState { current_flow, @@ -187,6 +187,7 @@ impl ErrorSwitch for ApiErrorRespon AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.clone()), ..Default::default() }))) } Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)), + Self::PaymentBlocked => AER::BadRequest(ApiError::new("HE", 3, "The payment is blocked", None)), Self::SuccessfulPaymentNotFound => { AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None)) } @@ -270,6 +271,9 @@ impl ErrorSwitch for ApiErrorRespon Self::InvalidConnectorConfiguration {config} => { AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) } + Self::CurrencyConversionFailed => { + AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) + } } } } diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 03ca8805610..330e02cd547 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -28,6 +28,8 @@ pub enum UserErrors { NameParsingError, #[error("PasswordParsingError")] PasswordParsingError, + #[error("UserAlreadyVerified")] + UserAlreadyVerified, #[error("CompanyNameParsingError")] CompanyNameParsingError, #[error("MerchantAccountCreationError: {0}")] @@ -104,6 +106,9 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 9, "Invalid Password", None)) } + Self::UserAlreadyVerified => { + AER::Unauthorized(ApiError::new(sub_code, 11, "User already verified", None)) + } Self::CompanyNameParsingError => { AER::BadRequest(ApiError::new(sub_code, 14, "Invalid Company Name", None)) } diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 55bd22baeec..ad3a7638774 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use api_models::{admin::FrmConfigs, enums as api_enums, payments::AdditionalPaymentData}; use error_stack::ResultExt; -use masking::PeekInterface; +use masking::{ExposeInterface, PeekInterface}; use router_env::{ logger, tracing::{self, instrument}, @@ -17,7 +17,6 @@ use self::{ }; use super::errors::{ConnectorErrorExt, RouterResponse}; use crate::{ - connector::signifyd::transformers::FrmFullfillmentSignifydApiRequest, core::{ errors::{self, RouterResult}, payments::{ @@ -52,7 +51,7 @@ pub mod types; pub async fn call_frm_service( state: &AppState, payment_data: &mut payments::PaymentData, - frm_data: FrmData, + frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, customer: &Option, @@ -78,7 +77,12 @@ where ) .await?; - let router_data = frm_data + frm_data.payment_attempt.connector_transaction_id = payment_data + .payment_attempt + .connector_transaction_id + .clone(); + + let mut router_data = frm_data .construct_router_data( state, &frm_data.connector_details.connector_name, @@ -88,6 +92,9 @@ where &merchant_connector_account, ) .await?; + + router_data.status = payment_data.payment_attempt.status; + let connector = FraudCheckConnectorData::get_connector_by_name(&frm_data.connector_details.connector_name)?; let router_data_res = router_data @@ -160,10 +167,9 @@ where match frm_configs_option { Some(frm_configs_value) => { let frm_configs_struct: Vec = frm_configs_value - .iter() + .into_iter() .map(|config| { config - .peek() - .clone() + .expose() .parse_value("FrmConfigs") .change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "frm_configs".to_string(), @@ -299,21 +305,21 @@ where // Panic Safety: we are first checking if the object is present... only if present, we try to fetch index 0 let frm_configs_object = FrmConfigsObject { frm_enabled_gateway: filtered_frm_config - .get(0) + .first() .and_then(|c| c.gateway), frm_enabled_pm: filtered_payment_methods - .get(0) + .first() .and_then(|pm| pm.payment_method), frm_enabled_pm_type: filtered_payment_method_types - .get(0) + .first() .and_then(|pmt| pmt.payment_method_type), frm_action: filtered_payment_method_types // .clone() - .get(0) + .first() .map(|pmt| pmt.action.clone()) .unwrap_or(api_enums::FrmAction::ManualReview), frm_preferred_flow_type: filtered_payment_method_types - .get(0) + .first() .map(|pmt| pmt.flow.clone()) .unwrap_or(api_enums::FrmPreferredFlowTypes::Pre), }; @@ -397,6 +403,7 @@ where address: payment_data.address.clone(), connector_details: frm_connector_details.clone(), order_details, + frm_metadata: payment_data.frm_metadata.clone(), }; let fraud_check_operation: operation::BoxedFraudCheckOperation = @@ -722,15 +729,14 @@ pub async fn make_fulfillment_api_call( frm_types::FraudCheckFulfillmentData, frm_types::FraudCheckResponseData, > = connector_data.connector.get_connector_integration(); - let modified_request_for_api_call = FrmFullfillmentSignifydApiRequest::from(req); let router_data = frm_flows::fulfillment_flow::construct_fulfillment_router_data( &state, &payment_intent, &payment_attempt, &merchant_account, &key_store, - "signifyd".to_string(), - modified_request_for_api_call, + fraud_check.frm_name.clone(), + req, ) .await?; let response = services::execute_connector_processing_step( diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index 47a29d65748..7f8993af527 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -1,9 +1,11 @@ use async_trait::async_trait; -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::ResultExt; +use masking::ExposeInterface; use super::{ConstructFlowSpecificData, FeatureFrm}; use crate::{ + connector::utils::PaymentsAttemptData, core::{ errors::{ConnectorErrorExt, RouterResult}, fraud_check::types::FrmData, @@ -15,7 +17,7 @@ use crate::{ domain, fraud_check::{FraudCheckCheckoutData, FraudCheckResponseData, FrmCheckoutRouterData}, storage::enums as storage_enums, - ConnectorAuthType, ResponseId, RouterData, + BrowserInformation, ConnectorAuthType, ResponseId, RouterData, }, AppState, }; @@ -43,6 +45,7 @@ impl ConstructFlowSpecificData = self.payment_attempt.get_browser_info().ok(); let customer_id = customer.to_owned().map(|customer| customer.customer_id); let router_data = RouterData { @@ -68,6 +71,27 @@ impl ConstructFlowSpecificData( + "AdditionalPaymentData", + ) + }) + .transpose() + .unwrap_or_default(), + email: customer.clone().and_then(|customer_data| { + customer_data + .email + .and_then(|email| Email::try_from(email.into_inner().expose()).ok()) + }), + gateway: self.payment_attempt.connector.clone(), }, // self.order_details response: Ok(FraudCheckResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId("".to_string()), @@ -94,6 +118,7 @@ impl ConstructFlowSpecificData( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, connector: String, - fulfillment_request: FrmFullfillmentSignifydApiRequest, + fulfillment_request: FrmFulfillmentRequest, ) -> RouterResult { let profile_id = core_utils::get_profile_id_from_business_details( payment_intent.business_country, @@ -79,7 +79,7 @@ pub async fn construct_fulfillment_router_data<'a>( request: FraudCheckFulfillmentData { amount: payment_attempt.amount, order_details: payment_intent.order_details.clone(), - fulfillment_request, + fulfillment_req: fulfillment_request, }, response: Err(ErrorResponse::default()), access_token: None, @@ -105,6 +105,7 @@ pub async fn construct_fulfillment_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index eaefdbefcc7..bd0ba3e4f7f 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -96,6 +96,7 @@ impl ConstructFlowSpecificData for FraudCheckPost { connector_details: payment_data.connector_details, order_details: payment_data.order_details, refund: None, + frm_metadata: payment_data.frm_metadata, }; Ok(Some(frm_data)) } @@ -152,7 +153,7 @@ impl Domain for FraudCheckPost { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -219,7 +220,7 @@ impl Domain for FraudCheckPost { let _router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -243,7 +244,7 @@ impl Domain for FraudCheckPost { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs index 00f50d01a86..b92df3d3ef9 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -120,6 +120,7 @@ impl GetTracker for FraudCheckPre { connector_details: payment_data.connector_details, order_details: payment_data.order_details, refund: None, + frm_metadata: payment_data.frm_metadata, }; Ok(Some(frm_data)) } @@ -146,7 +147,7 @@ impl Domain for FraudCheckPre { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -163,6 +164,9 @@ impl Domain for FraudCheckPre { order_details: router_data.request.order_details, currency: router_data.request.currency, payment_method: Some(router_data.payment_method), + error_code: router_data.request.error_code, + error_message: router_data.request.error_message, + connector_transaction_id: router_data.request.connector_transaction_id, }), response: FrmResponse::Transaction(router_data.response), })) @@ -180,7 +184,7 @@ impl Domain for FraudCheckPre { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -195,6 +199,11 @@ impl Domain for FraudCheckPre { request: FrmRequest::Checkout(FraudCheckCheckoutData { amount: router_data.request.amount, order_details: router_data.request.order_details, + currency: router_data.request.currency, + browser_info: router_data.request.browser_info, + payment_method_data: router_data.request.payment_method_data, + email: router_data.request.email, + gateway: router_data.request.gateway, }), response: FrmResponse::Checkout(router_data.response), }) diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index 1d6e7cb45a5..e60458646f3 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -56,6 +56,7 @@ pub struct FrmData { pub connector_details: ConnectorDetailsCore, pub order_details: Option>, pub refund: Option, + pub frm_metadata: Option, } #[derive(Debug)] @@ -79,6 +80,7 @@ pub struct PaymentToFrmData { pub address: PaymentAddress, pub connector_details: ConnectorDetailsCore, pub order_details: Option>, + pub frm_metadata: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -123,6 +125,14 @@ pub struct FrmFulfillmentRequest { ///contains details of the fulfillment #[schema(value_type = Vec)] pub fulfillments: Vec, + //name of the tracking Company + #[schema(max_length = 255, example = "fedex")] + pub tracking_company: Option, + //tracking ID of the product + #[schema(max_length = 255, example = "track_8327446667")] + pub tracking_number: Option, + //tracking_url for tracking the product + pub tracking_url: Option, } #[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index 3f56cddee12..e3e308a8a01 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -109,6 +109,7 @@ pub async fn call_to_locker( payment_method_issuer: pm.payment_method_issuer, payment_method_issuer_code: pm.payment_method_issuer_code, card: Some(card_details.clone()), + bank_transfer: None, metadata: pm.metadata, customer_id: Some(pm.customer_id), card_network: card.card_brand, diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 55de11549a4..aabd846660c 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -1,13 +1,18 @@ +pub mod utils; + use api_models::payments; use common_utils::{ext_traits::Encode, pii}; use diesel_models::enums as storage_enums; -use error_stack::{report, ResultExt}; +use error_stack::{report, IntoReport, ResultExt}; use futures::future; use router_env::{instrument, logger, tracing}; use super::payments::helpers; use crate::{ - core::errors::{self, RouterResponse, StorageErrorExt}, + core::{ + errors::{self, RouterResponse, StorageErrorExt}, + payments::CallConnectorAction, + }, db::StorageInterface, routes::{metrics, AppState}, services, @@ -16,6 +21,7 @@ use crate::{ api::{ customers, mandates::{self, MandateResponseExt}, + ConnectorData, GetToken, }, domain, storage, transformers::ForeignTryFrom, @@ -44,26 +50,121 @@ pub async fn get_mandate( pub async fn revoke_mandate( state: AppState, merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, req: mandates::MandateId, ) -> RouterResponse { let db = state.store.as_ref(); let mandate = db - .update_mandate_by_merchant_id_mandate_id( - &merchant_account.merchant_id, - &req.mandate_id, - storage::MandateUpdate::StatusUpdate { - mandate_status: storage::enums::MandateStatus::Revoked, - }, - ) + .find_mandate_by_merchant_id_mandate_id(&merchant_account.merchant_id, &req.mandate_id) .await .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; - Ok(services::ApplicationResponse::Json( - mandates::MandateRevokedResponse { - mandate_id: mandate.mandate_id, - status: mandate.mandate_status, - }, - )) + let mandate_revoke_status = match mandate.mandate_status { + common_enums::MandateStatus::Active + | common_enums::MandateStatus::Inactive + | common_enums::MandateStatus::Pending => { + let profile_id = if let Some(ref payment_id) = mandate.original_payment_id { + let pi = db + .find_payment_intent_by_payment_id_merchant_id( + payment_id, + &merchant_account.merchant_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + let profile_id = pi.profile_id.clone().ok_or( + errors::ApiErrorResponse::BusinessProfileNotFound { + id: pi + .profile_id + .unwrap_or_else(|| "Profile id is Null".to_string()), + }, + )?; + Ok(profile_id) + } else { + Err(errors::ApiErrorResponse::PaymentNotFound) + }?; + + let merchant_connector_account = helpers::get_merchant_connector_account( + &state, + &merchant_account.merchant_id, + None, + &key_store, + &profile_id, + &mandate.connector, + mandate.merchant_connector_id.as_ref(), + ) + .await?; + + let connector_data = ConnectorData::get_connector_by_name( + &state.conf.connectors, + &mandate.connector, + GetToken::Connector, + mandate.merchant_connector_id.clone(), + )?; + let connector_integration: services::BoxedConnectorIntegration< + '_, + types::api::MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + > = connector_data.connector.get_connector_integration(); + + let router_data = utils::construct_mandate_revoke_router_data( + merchant_connector_account, + &merchant_account, + mandate.clone(), + ) + .await?; + + let response = services::execute_connector_processing_step( + &state, + connector_integration, + &router_data, + CallConnectorAction::Trigger, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + match response.response { + Ok(_) => { + let update_mandate = db + .update_mandate_by_merchant_id_mandate_id( + &merchant_account.merchant_id, + &req.mandate_id, + storage::MandateUpdate::StatusUpdate { + mandate_status: storage::enums::MandateStatus::Revoked, + }, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; + Ok(services::ApplicationResponse::Json( + mandates::MandateRevokedResponse { + mandate_id: update_mandate.mandate_id, + status: update_mandate.mandate_status, + error_code: None, + error_message: None, + }, + )) + } + + Err(err) => Err(errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: mandate.connector, + status_code: err.status_code, + reason: err.reason, + }) + .into_report(), + } + } + common_enums::MandateStatus::Revoked => { + Err(errors::ApiErrorResponse::MandateValidationFailed { + reason: "Mandate has already been revoked".to_string(), + }) + .into_report() + } + }; + mandate_revoke_status } #[instrument(skip(db))] diff --git a/crates/router/src/core/mandate/utils.rs b/crates/router/src/core/mandate/utils.rs new file mode 100644 index 00000000000..25267db1a96 --- /dev/null +++ b/crates/router/src/core/mandate/utils.rs @@ -0,0 +1,76 @@ +use std::marker::PhantomData; + +use common_utils::{errors::CustomResult, ext_traits::ValueExt}; +use diesel_models::Mandate; +use error_stack::ResultExt; + +use crate::{ + core::{errors, payments::helpers}, + types::{self, domain, PaymentAddress}, +}; +const IRRELEVANT_PAYMENT_ID_IN_MANDATE_REVOKE_FLOW: &str = + "irrelevant_payment_id_in_mandate_revoke_flow"; + +const IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW: &str = + "irrelevant_attempt_id_in_mandate_revoke_flow"; + +const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW: &str = + "irrelevant_connector_request_reference_id_in_mandate_revoke_flow"; + +pub async fn construct_mandate_revoke_router_data( + merchant_connector_account: helpers::MerchantConnectorAccountType, + merchant_account: &domain::MerchantAccount, + mandate: Mandate, +) -> CustomResult { + let auth_type: types::ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + customer_id: Some(mandate.customer_id), + connector_customer: None, + connector: mandate.connector, + payment_id: mandate + .original_payment_id + .unwrap_or_else(|| IRRELEVANT_PAYMENT_ID_IN_MANDATE_REVOKE_FLOW.to_string()), + attempt_id: IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW.to_string(), + status: diesel_models::enums::AttemptStatus::default(), + payment_method: diesel_models::enums::PaymentMethod::default(), + connector_auth_type: auth_type, + description: None, + return_url: None, + address: PaymentAddress::default(), + auth_type: diesel_models::enums::AuthenticationType::default(), + connector_meta_data: None, + amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + recurring_mandate_payment_data: None, + preprocessing_id: None, + payment_method_balance: None, + connector_api_version: None, + request: types::MandateRevokeRequestData { + mandate_id: mandate.mandate_id, + connector_mandate_id: mandate.connector_mandate_id, + }, + response: Err(types::ErrorResponse::get_not_implemented()), + payment_method_id: None, + connector_request_reference_id: + IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW.to_string(), + test_mode: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + }; + + Ok(router_data) +} diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 07fdf4ae407..84cd726a7e4 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -1,7 +1,8 @@ use api_models::admin as admin_types; use common_utils::{ consts::{ - DEFAULT_BACKGROUND_COLOR, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_THEME, + DEFAULT_BACKGROUND_COLOR, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, + DEFAULT_SESSION_EXPIRY, }, ext_traits::{OptionExt, ValueExt}, }; @@ -12,7 +13,6 @@ use time::PrimitiveDateTime; use super::errors::{self, RouterResult, StorageErrorExt}; use crate::{ - core::payments::helpers, errors::RouterResponse, routes::AppState, services, @@ -27,15 +27,20 @@ pub async fn retrieve_payment_link( payment_link_id: String, ) -> RouterResponse { let db = &*state.store; - let payment_link_object = db + let payment_link_config = db .find_payment_link_by_payment_link_id(&payment_link_id) .await .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; - let status = check_payment_link_status(payment_link_object.fulfilment_time); + let session_expiry = payment_link_config.fulfilment_time.unwrap_or_else(|| { + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) + }); + + let status = check_payment_link_status(session_expiry); let response = api_models::payments::RetrievePaymentLinkResponse::foreign_from(( - payment_link_object, + payment_link_config, status, )); Ok(services::ApplicationResponse::Json(response)) @@ -62,32 +67,29 @@ pub async fn intiate_payment_link_flow( .get_required_value("payment_link_id") .change_context(errors::ApiErrorResponse::PaymentLinkNotFound)?; - helpers::validate_payment_status_against_not_allowed_statuses( - &payment_intent.status, - &[ - storage_enums::IntentStatus::Cancelled, - storage_enums::IntentStatus::Succeeded, - storage_enums::IntentStatus::Processing, - storage_enums::IntentStatus::RequiresCapture, - storage_enums::IntentStatus::RequiresMerchantAction, - ], - "use payment link for", - )?; + let merchant_name_from_merchant_account = merchant_account + .merchant_name + .clone() + .map(|merchant_name| merchant_name.into_inner().peek().to_owned()) + .unwrap_or_default(); let payment_link = db .find_payment_link_by_payment_link_id(&payment_link_id) .await .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; - let payment_link_config = if let Some(pl_config) = payment_link.payment_link_config.clone() { - extract_payment_link_config(Some(pl_config))? + let payment_link_config = if let Some(pl_config_value) = payment_link.payment_link_config { + extract_payment_link_config(pl_config_value)? } else { - extract_payment_link_config(merchant_account.payment_link_config.clone())? + admin_types::PaymentLinkConfig { + theme: DEFAULT_BACKGROUND_COLOR.to_string(), + logo: DEFAULT_MERCHANT_LOGO.to_string(), + seller_name: merchant_name_from_merchant_account, + sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(), + } }; - let order_details = validate_order_details(payment_intent.order_details)?; - - let return_url = if let Some(payment_create_return_url) = payment_intent.return_url { + let return_url = if let Some(payment_create_return_url) = payment_intent.return_url.clone() { payment_create_return_url } else { merchant_account @@ -100,55 +102,98 @@ pub async fn intiate_payment_link_flow( let (pub_key, currency, client_secret) = validate_sdk_requirements( merchant_account.publishable_key, payment_intent.currency, - payment_intent.client_secret, + payment_intent.client_secret.clone(), )?; + let amount = currency + .to_currency_base_unit(payment_intent.amount) + .into_report() + .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?; + let order_details = validate_order_details(payment_intent.order_details.clone(), currency)?; + + let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| { + payment_intent + .created_at + .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) + }); - let (default_sdk_theme, default_background_color) = - (DEFAULT_SDK_THEME, DEFAULT_BACKGROUND_COLOR); + // converting first letter of merchant name to upperCase + let merchant_name = capitalize_first_char(&payment_link_config.seller_name); + let css_script = get_color_scheme_css(payment_link_config.clone()); + let payment_link_status = check_payment_link_status(session_expiry); + + if check_payment_link_invalid_conditions( + &payment_intent.status, + &[ + storage_enums::IntentStatus::Cancelled, + storage_enums::IntentStatus::Failed, + storage_enums::IntentStatus::Processing, + storage_enums::IntentStatus::RequiresCapture, + storage_enums::IntentStatus::RequiresMerchantAction, + storage_enums::IntentStatus::Succeeded, + ], + ) || payment_link_status == api_models::payments::PaymentLinkStatus::Expired + { + let attempt_id = payment_intent.active_attempt.get_id().clone(); + let payment_attempt = db + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + &merchant_id, + &attempt_id.clone(), + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let payment_details = api_models::payments::PaymentLinkStatusDetails { + amount, + currency, + payment_id: payment_intent.payment_id, + merchant_name, + merchant_logo: payment_link_config.clone().logo, + created: payment_link.created_at, + intent_status: payment_intent.status, + payment_link_status, + error_code: payment_attempt.error_code, + error_message: payment_attempt.error_message, + }; + let js_script = get_js_script( + api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details), + )?; + let payment_link_error_data = services::PaymentLinkStatusData { + js_script, + css_script, + }; + return Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new( + services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data), + ))); + }; let payment_details = api_models::payments::PaymentLinkDetails { - amount: payment_intent.amount, + amount, currency, payment_id: payment_intent.payment_id, - merchant_name: payment_link.custom_merchant_name.unwrap_or( - merchant_account - .merchant_name - .map(|merchant_name| merchant_name.into_inner().peek().to_owned()) - .unwrap_or_default(), - ), + merchant_name, order_details, return_url, - expiry: payment_link.fulfilment_time, + session_expiry, pub_key, client_secret, - merchant_logo: payment_link_config - .clone() - .map(|pl_config| { - pl_config - .merchant_logo - .unwrap_or(DEFAULT_MERCHANT_LOGO.to_string()) - }) - .unwrap_or_default(), + merchant_logo: payment_link_config.clone().logo, max_items_visible_after_collapse: 3, - sdk_theme: payment_link_config.clone().and_then(|pl_config| { - pl_config - .color_scheme - .map(|color| color.sdk_theme.unwrap_or(default_sdk_theme.to_string())) - }), + theme: payment_link_config.clone().theme, + merchant_description: payment_intent.description, + sdk_layout: payment_link_config.clone().sdk_layout, }; - let js_script = get_js_script(payment_details)?; - let css_script = get_color_scheme_css( - payment_link_config.clone(), - default_background_color.to_string(), - ); + let js_script = get_js_script(api_models::payments::PaymentLinkData::PaymentLinkDetails( + payment_details, + ))?; let payment_link_data = services::PaymentLinkFormData { js_script, sdk_url: state.conf.payment_link.sdk_url.clone(), css_script, }; Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new( - payment_link_data, + services::api::PaymentLinkAction::PaymentLinkFormData(payment_link_data), ))) } @@ -156,30 +201,16 @@ pub async fn intiate_payment_link_flow( The get_js_script function is used to inject dynamic value to payment_link sdk, which is unique to every payment. */ -fn get_js_script( - payment_details: api_models::payments::PaymentLinkDetails, -) -> RouterResult { +fn get_js_script(payment_details: api_models::payments::PaymentLinkData) -> RouterResult { let payment_details_str = serde_json::to_string(&payment_details) .into_report() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to serialize PaymentLinkDetails")?; + .attach_printable("Failed to serialize PaymentLinkData")?; Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};")) } -fn get_color_scheme_css( - payment_link_config: Option, - default_primary_color: String, -) -> String { - let background_primary_color = payment_link_config - .and_then(|pl_config| { - pl_config.color_scheme.map(|color| { - color - .background_primary_color - .unwrap_or(default_primary_color.clone()) - }) - }) - .unwrap_or(default_primary_color); - +fn get_color_scheme_css(payment_link_config: api_models::admin::PaymentLinkConfig) -> String { + let background_primary_color = payment_link_config.theme; format!( ":root {{ --primary-color: {background_primary_color}; @@ -224,20 +255,23 @@ pub async fn list_payment_link( Ok(services::ApplicationResponse::Json(payment_link_list)) } -pub fn check_payment_link_status(fulfillment_time: Option) -> String { - let curr_time = Some(common_utils::date_time::now()); +pub fn check_payment_link_status( + payment_link_expiry: PrimitiveDateTime, +) -> api_models::payments::PaymentLinkStatus { + let curr_time = common_utils::date_time::now(); - if curr_time > fulfillment_time { - "expired".to_string() + if curr_time > payment_link_expiry { + api_models::payments::PaymentLinkStatus::Expired } else { - "active".to_string() + api_models::payments::PaymentLinkStatus::Active } } fn validate_order_details( order_details: Option>>, + currency: api_models::enums::Currency, ) -> Result< - Option>, + Option>, error_stack::Report, > { let order_details = order_details @@ -256,27 +290,138 @@ fn validate_order_details( }) .transpose()?; - let updated_order_details = order_details.map(|mut order_details| { - for order in order_details.iter_mut() { - if order.product_img_link.is_none() { - order.product_img_link = Some(DEFAULT_PRODUCT_IMG.to_string()); + let updated_order_details = match order_details { + Some(mut order_details) => { + let mut order_details_amount_string_array: Vec< + api_models::payments::OrderDetailsWithStringAmount, + > = Vec::new(); + for order in order_details.iter_mut() { + let mut order_details_amount_string : api_models::payments::OrderDetailsWithStringAmount = Default::default(); + if order.product_img_link.is_none() { + order_details_amount_string.product_img_link = + Some(DEFAULT_PRODUCT_IMG.to_string()) + } else { + order_details_amount_string.product_img_link = order.product_img_link.clone() + }; + order_details_amount_string.amount = currency + .to_currency_base_unit(order.amount) + .into_report() + .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?; + order_details_amount_string.product_name = + capitalize_first_char(&order.product_name.clone()); + order_details_amount_string.quantity = order.quantity; + order_details_amount_string_array.push(order_details_amount_string) } + Some(order_details_amount_string_array) } - order_details - }); + None => None, + }; Ok(updated_order_details) } -fn extract_payment_link_config( - pl_config: Option, -) -> Result, error_stack::Report> { - pl_config - .map(|config| { - serde_json::from_value::(config) - .into_report() - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_link_config", - }) +pub fn extract_payment_link_config( + pl_config: serde_json::Value, +) -> Result> { + serde_json::from_value::(pl_config.clone()) + .into_report() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", + }) +} + +pub fn get_payment_link_config_based_on_priority( + payment_create_link_config: Option, + business_link_config: Option, + merchant_name: String, + default_domain_name: String, +) -> Result<(admin_types::PaymentLinkConfig, String), error_stack::Report> +{ + let (domain_name, business_config) = if let Some(business_config) = business_link_config { + let extracted_value: api_models::admin::BusinessPaymentLinkConfig = business_config + .parse_value("BusinessPaymentLinkConfig") + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", + }) + .attach_printable("Invalid payment_link_config given in business config")?; + + ( + extracted_value + .domain_name + .clone() + .map(|d_name| format!("https://{}", d_name)) + .unwrap_or_else(|| default_domain_name.clone()), + Some(extracted_value.config), + ) + } else { + (default_domain_name, None) + }; + + let theme = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.theme.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.theme.clone()) + }) + .unwrap_or(DEFAULT_BACKGROUND_COLOR.to_string()); + + let logo = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.logo.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.logo.clone()) }) - .transpose() + .unwrap_or(DEFAULT_MERCHANT_LOGO.to_string()); + + let seller_name = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.seller_name.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.seller_name.clone()) + }) + .unwrap_or(merchant_name.clone()); + + let sdk_layout = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.sdk_layout.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.sdk_layout.clone()) + }) + .unwrap_or(DEFAULT_SDK_LAYOUT.to_owned()); + + let payment_link_config = admin_types::PaymentLinkConfig { + theme, + logo, + seller_name, + sdk_layout, + }; + + Ok((payment_link_config, domain_name)) +} + +fn capitalize_first_char(s: &str) -> String { + if let Some(first_char) = s.chars().next() { + let capitalized = first_char.to_uppercase(); + let mut result = capitalized.to_string(); + if let Some(remaining) = s.get(1..) { + result.push_str(remaining); + } + result + } else { + s.to_owned() + } +} + +fn check_payment_link_invalid_conditions( + intent_status: &storage_enums::IntentStatus, + not_allowed_statuses: &[storage_enums::IntentStatus], +) -> bool { + not_allowed_statuses.contains(intent_status) } diff --git a/crates/router/src/core/payment_link/payment_link.html b/crates/router/src/core/payment_link/payment_link.html index 0ca4abd340d..f6e62f8bdc8 100644 --- a/crates/router/src/core/payment_link/payment_link.html +++ b/crates/router/src/core/payment_link/payment_link.html @@ -1,9 +1,9 @@ - + - {{ hyperloader_sdk_link }} + Payments requested by HyperSwitch + + + + + +
+
+
+
+
+
+ + diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 14a39f1d955..c1617fa77c9 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -43,6 +43,7 @@ pub trait PaymentMethodRetrieve { token: &storage::PaymentTokenData, payment_intent: &PaymentIntent, card_token_data: Option<&CardToken>, + customer: &Option, ) -> RouterResult>; } @@ -126,6 +127,7 @@ impl PaymentMethodRetrieve for Oss { token_data: &storage::PaymentTokenData, payment_intent: &PaymentIntent, card_token_data: Option<&CardToken>, + customer: &Option, ) -> RouterResult> { match token_data { storage::PaymentTokenData::TemporaryGeneric(generic_token) => { @@ -178,6 +180,7 @@ impl PaymentMethodRetrieve for Oss { merchant_key_store, auth_token, payment_intent, + customer, ) .await } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 84aef952a53..51f54353635 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -21,7 +21,10 @@ use common_utils::{ ext_traits::{AsyncExt, StringExt, ValueExt}, generate_id, }; -use diesel_models::{encryption::Encryption, enums as storage_enums, payment_method}; +use diesel_models::{ + business_profile::BusinessProfile, encryption::Encryption, enums as storage_enums, + payment_method, +}; use error_stack::{report, IntoReport, ResultExt}; use masking::Secret; use router_env::{instrument, tracing}; @@ -44,6 +47,7 @@ use crate::{ helpers, routing::{self, SessionFlowRoutingInput}, }, + utils as core_utils, }, db, logger, pii::prelude::*, @@ -101,6 +105,29 @@ pub async fn create_payment_method( Ok(response) } +pub fn store_default_payment_method( + req: &api::PaymentMethodCreate, + customer_id: &str, + merchant_id: &String, +) -> (api::PaymentMethodResponse, bool) { + let pm_id = generate_id(consts::ID_LENGTH, "pm"); + let payment_method_response = api::PaymentMethodResponse { + merchant_id: merchant_id.to_string(), + customer_id: Some(customer_id.to_owned()), + payment_method_id: pm_id, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + bank_transfer: None, + card: None, + metadata: req.metadata.clone(), + created: Some(common_utils::date_time::now()), + recurring_enabled: false, //[#219] + installment_payment_enabled: false, //[#219] + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219] + }; + (payment_method_response, false) +} + #[instrument(skip_all)] pub async fn add_payment_method( state: routes::AppState, @@ -111,30 +138,44 @@ pub async fn add_payment_method( req.validate()?; let merchant_id = &merchant_account.merchant_id; let customer_id = req.customer_id.clone().get_required_value("customer_id")?; - let response = match req.card.clone() { - Some(card) => { - add_card_to_locker(&state, req.clone(), &card, &customer_id, merchant_account) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Add Card Failed") - } - None => { - let pm_id = generate_id(consts::ID_LENGTH, "pm"); - let payment_method_response = api::PaymentMethodResponse { - merchant_id: merchant_id.to_string(), - customer_id: Some(customer_id.clone()), - payment_method_id: pm_id, - payment_method: req.payment_method, - payment_method_type: req.payment_method_type, - card: None, - metadata: req.metadata.clone(), - created: Some(common_utils::date_time::now()), - recurring_enabled: false, //[#219] - installment_payment_enabled: false, //[#219] - payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), //[#219] - }; - Ok((payment_method_response, false)) - } + + let response = match req.payment_method { + api_enums::PaymentMethod::BankTransfer => match req.bank_transfer.clone() { + Some(bank) => add_bank_to_locker( + &state, + req.clone(), + merchant_account, + key_store, + &bank, + &customer_id, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add PaymentMethod Failed"), + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + api_enums::PaymentMethod::Card => match req.card.clone() { + Some(card) => { + add_card_to_locker(&state, req.clone(), &card, &customer_id, merchant_account) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Add Card Failed") + } + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), + }, + _ => Ok(store_default_payment_method( + &req, + &customer_id, + merchant_id, + )), }; let (resp, is_duplicate) = response?; @@ -195,6 +236,7 @@ pub async fn update_customer_payment_method( payment_method_type: pm.payment_method_type, payment_method_issuer: pm.payment_method_issuer, payment_method_issuer_code: pm.payment_method_issuer_code, + bank_transfer: req.bank_transfer, card: req.card, metadata: req.metadata, customer_id: Some(pm.customer_id), @@ -208,6 +250,64 @@ pub async fn update_customer_payment_method( // Wrapper function to switch lockers +pub async fn add_bank_to_locker( + state: &routes::AppState, + req: api::PaymentMethodCreate, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + bank: &api::BankPayout, + customer_id: &String, +) -> errors::CustomResult<(api::PaymentMethodResponse, bool), errors::VaultError> { + let key = key_store.key.get_inner().peek(); + let payout_method_data = api::PayoutMethodData::Bank(bank.clone()); + let enc_data = async { + serde_json::to_value(payout_method_data.to_owned()) + .map_err(|err| { + logger::error!("Error while encoding payout method data: {}", err); + errors::VaultError::SavePaymentMethodFailed + }) + .into_report() + .change_context(errors::VaultError::SavePaymentMethodFailed) + .attach_printable("Unable to encode payout method data") + .ok() + .map(|v| { + let secret: Secret = Secret::new(v.to_string()); + secret + }) + .async_lift(|inner| encrypt_optional(inner, key)) + .await + } + .await + .change_context(errors::VaultError::SavePaymentMethodFailed) + .attach_printable("Failed to encrypt payout method data")? + .map(Encryption::from) + .map(|e| e.into_inner()) + .map_or(Err(errors::VaultError::SavePaymentMethodFailed), |e| { + Ok(hex::encode(e.peek())) + })?; + + let payload = + payment_methods::StoreLockerReq::LockerGeneric(payment_methods::StoreGenericReq { + merchant_id: &merchant_account.merchant_id, + merchant_customer_id: customer_id.to_owned(), + enc_data, + }); + let store_resp = call_to_locker_hs( + state, + &payload, + customer_id, + api_enums::LockerChoice::Basilisk, + ) + .await?; + let payment_method_resp = payment_methods::mk_add_bank_response_hs( + bank.clone(), + store_resp.card_reference, + req, + &merchant_account.merchant_id, + ); + Ok((payment_method_resp, store_resp.duplicate.unwrap_or(false))) +} + /// The response will be the tuple of PaymentMethodResponse and the duplication check of payment_method pub async fn add_card_to_locker( state: &routes::AppState, @@ -1077,6 +1177,12 @@ pub async fn list_payment_methods( }) .await .transpose()?; + let business_profile = core_utils::validate_and_get_business_profile( + db, + profile_id.as_ref(), + &merchant_account.merchant_id, + ) + .await?; // filter out connectors based on the business country let filtered_mcas = helpers::filter_mca_based_on_business_profile(all_mcas, profile_id); @@ -1690,13 +1796,18 @@ pub async fn list_payment_methods( payment_method_types: bank_transfer_payment_method_types, }); } - let merchant_surcharge_configs = - if let Some((attempt, payment_intent)) = payment_attempt.as_ref().zip(payment_intent) { + if let Some((payment_attempt, payment_intent, business_profile)) = payment_attempt + .as_ref() + .zip(payment_intent) + .zip(business_profile) + .map(|((pa, pi), bp)| (pa, pi, bp)) + { Box::pin(call_surcharge_decision_management( state, &merchant_account, - attempt, + &business_profile, + payment_attempt, payment_intent, billing_address, &mut payment_method_responses, @@ -1705,7 +1816,6 @@ pub async fn list_payment_methods( } else { api_surcharge_decision_configs::MerchantSurchargeConfigs::default() }; - Ok(services::ApplicationResponse::Json( api::PaymentMethodListResponse { redirect_url: merchant_account.return_url, @@ -1747,6 +1857,7 @@ pub async fn list_payment_methods( pub async fn call_surcharge_decision_management( state: routes::AppState, merchant_account: &domain::MerchantAccount, + business_profile: &BusinessProfile, payment_attempt: &storage::PaymentAttempt, payment_intent: storage::PaymentIntent, billing_address: Option, @@ -1777,7 +1888,7 @@ pub async fn call_surcharge_decision_management( .attach_printable("error performing surcharge decision operation")?; if !surcharge_results.is_empty_result() { surcharge_results - .persist_individual_surcharge_details_in_redis(&state, merchant_account) + .persist_individual_surcharge_details_in_redis(&state, business_profile) .await?; let _ = state .store @@ -1800,6 +1911,7 @@ pub async fn call_surcharge_decision_management( pub async fn call_surcharge_decision_management_for_saved_card( state: &routes::AppState, merchant_account: &domain::MerchantAccount, + business_profile: &BusinessProfile, payment_attempt: &storage::PaymentAttempt, payment_intent: storage::PaymentIntent, customer_payment_method_response: &mut api::CustomerPaymentMethodsListResponse, @@ -1827,7 +1939,7 @@ pub async fn call_surcharge_decision_management_for_saved_card( .attach_printable("error performing surcharge decision operation")?; if !surcharge_results.is_empty_result() { surcharge_results - .persist_individual_surcharge_details_in_redis(state, merchant_account) + .persist_individual_surcharge_details_in_redis(state, business_profile) .await?; let _ = state .store @@ -2237,7 +2349,7 @@ fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Optio // (Some(amt), Some(max_amt)) => amt <= max_amt, // (_, _) => true, // }; - min_check && max_check + (min_check && max_check) || amount == Some(0) } fn filter_pm_based_on_allowed_types( @@ -2296,8 +2408,9 @@ fn filter_payment_amount_based( pm: &RequestPaymentMethodTypes, ) -> bool { let amount = payment_intent.amount; - pm.maximum_amount.map_or(true, |amt| amount < amt.into()) - && pm.minimum_amount.map_or(true, |amt| amount > amt.into()) + (pm.maximum_amount.map_or(true, |amt| amount <= amt.into()) + && pm.minimum_amount.map_or(true, |amt| amount >= amt.into())) + || payment_intent.amount == 0 } async fn filter_payment_mandate_based( @@ -2407,7 +2520,17 @@ pub async fn list_customer_payment_method( let token_data = PaymentTokenData::temporary_generic(token.clone()); ( None, - Some(get_lookup_key_for_payout_method(state, &key_store, &token, &pm).await?), + Some( + get_bank_from_hs_locker( + state, + &key_store, + &token, + &pm.customer_id, + &pm.customer_id, + &pm.payment_method_id, + ) + .await?, + ), token_data, ) } @@ -2535,10 +2658,38 @@ pub async fn list_customer_payment_method( .await .transpose()?; - if let Some((payment_attempt, payment_intent)) = payment_attempt.zip(payment_intent) { + let profile_id = payment_intent + .as_ref() + .async_map(|payment_intent| async { + crate::core::utils::get_profile_id_from_business_details( + payment_intent.business_country, + payment_intent.business_label.as_ref(), + &merchant_account, + payment_intent.profile_id.as_ref(), + db, + false, + ) + .await + .attach_printable("Could not find profile id from business details") + }) + .await + .transpose()?; + let business_profile = core_utils::validate_and_get_business_profile( + db, + profile_id.as_ref(), + &merchant_account.merchant_id, + ) + .await?; + + if let Some((payment_attempt, payment_intent, business_profile)) = payment_attempt + .zip(payment_intent) + .zip(business_profile) + .map(|((pa, pi), bp)| (pa, pi, bp)) + { call_surcharge_decision_management_for_saved_card( state, &merchant_account, + &business_profile, &payment_attempt, payment_intent, &mut response, @@ -2693,18 +2844,20 @@ async fn get_bank_account_connector_details( } #[cfg(feature = "payouts")] -pub async fn get_lookup_key_for_payout_method( +pub async fn get_bank_from_hs_locker( state: &routes::AppState, key_store: &domain::MerchantKeyStore, - payout_token: &str, - pm: &storage::PaymentMethod, + temp_token: &str, + customer_id: &str, + merchant_id: &str, + token_ref: &str, ) -> errors::RouterResult { let payment_method = get_payment_method_from_hs_locker( state, key_store, - &pm.customer_id, - &pm.merchant_id, - &pm.payment_method_id, + customer_id, + merchant_id, + token_ref, None, ) .await @@ -2719,9 +2872,9 @@ pub async fn get_lookup_key_for_payout_method( api::PayoutMethodData::Bank(bank) => { vault::Vault::store_payout_method_data_in_locker( state, - Some(payout_token.to_string()), + Some(temp_token.to_string()), &pm_parsed, - Some(pm.customer_id.to_owned()), + Some(customer_id.to_owned()), key_store, ) .await @@ -2848,6 +3001,7 @@ pub async fn retrieve_payment_method( payment_method_id: pm.payment_method_id, payment_method: pm.payment_method, payment_method_type: pm.payment_method_type, + bank_transfer: None, card, metadata: pm.metadata, created: Some(pm.created_at), diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index e130795e945..db1064b36a7 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -310,6 +310,7 @@ fn get_surcharge_details_from_surcharge_output( .transpose()? .unwrap_or(0); Ok(types::SurchargeDetails { + original_amount: payment_attempt.amount, surcharge: match surcharge_details.surcharge { surcharge_decision_configs::SurchargeOutput::Fixed { amount } => { common_utils_types::Surcharge::Fixed(amount) diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 3b4d057e602..da4f03b49c1 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use api_models::enums as api_enums; -use common_utils::{ext_traits::StringExt, pii::Email}; +use common_utils::{ext_traits::StringExt, pii::Email, request::RequestContent}; use error_stack::ResultExt; use josekit::jwe; use serde::{Deserialize, Serialize}; @@ -97,7 +97,7 @@ pub struct DeleteCardResp { #[serde(rename_all = "camelCase")] pub struct AddCardRequest<'a> { pub card_number: cards::CardNumber, - pub customer_id: &'a str, + pub customer_id: String, pub card_exp_month: Secret, pub card_exp_year: Secret, pub merchant_id: &'a str, @@ -313,9 +313,6 @@ pub async fn mk_add_locker_request_hs<'a>( .change_context(errors::VaultError::RequestEncodingFailed)?; let jwe_payload = mk_basilisk_req(jwekey, &jws, locker_choice).await?; - - let body = utils::Encode::::encode_to_value(&jwe_payload) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = match locker_choice { api_enums::LockerChoice::Basilisk => locker.host.to_owned(), api_enums::LockerChoice::Tartarus => locker.host_rs.to_owned(), @@ -323,10 +320,32 @@ pub async fn mk_add_locker_request_hs<'a>( url.push_str("/cards/add"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(jwe_payload))); Ok(request) } +pub fn mk_add_bank_response_hs( + bank: api::BankPayout, + bank_reference: String, + req: api::PaymentMethodCreate, + merchant_id: &str, +) -> api::PaymentMethodResponse { + api::PaymentMethodResponse { + merchant_id: merchant_id.to_owned(), + customer_id: req.customer_id, + payment_method_id: bank_reference, + payment_method: req.payment_method, + payment_method_type: req.payment_method_type, + bank_transfer: Some(bank), + card: None, + metadata: req.metadata, + created: Some(common_utils::date_time::now()), + recurring_enabled: false, // [#256] + installment_payment_enabled: false, // #[#256] + payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), // [#256] + } +} + pub fn mk_add_card_response_hs( card: api::CardDetail, card_reference: String, @@ -352,6 +371,7 @@ pub fn mk_add_card_response_hs( payment_method_id: card_reference, payment_method: req.payment_method, payment_method_type: req.payment_method_type, + bank_transfer: None, card: Some(card), metadata: req.metadata, created: Some(common_utils::date_time::now()), @@ -386,6 +406,7 @@ pub fn mk_add_card_response( payment_method_id: response.card_id, payment_method: req.payment_method, payment_method_type: req.payment_method_type, + bank_transfer: None, card: Some(card), metadata: req.metadata, created: Some(common_utils::date_time::now()), @@ -400,7 +421,7 @@ pub fn mk_add_card_request( card: &api::CardDetail, customer_id: &str, _req: &api::PaymentMethodCreate, - locker_id: &str, + locker_id: &'static str, merchant_id: &str, ) -> CustomResult { let customer_id = if cfg!(feature = "release") { @@ -410,7 +431,7 @@ pub fn mk_add_card_request( }; let add_card_req = AddCardRequest { card_number: card.card_number.clone(), - customer_id: &customer_id, + customer_id, card_exp_month: card.card_exp_month.clone(), card_exp_year: card.card_exp_year.clone(), merchant_id: locker_id, @@ -421,16 +442,10 @@ pub fn mk_add_card_request( name_on_card: Some("John Doe".to_string().into()), // [#256] nickname: Some("router".to_string()), // }; - let body = utils::Encode::>::url_encode(&add_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/addCard"); let mut request = services::Request::new(services::Method::Post, &url); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(add_card_req))); Ok(request) } @@ -465,9 +480,6 @@ pub async fn mk_get_card_request_hs( let target_locker = locker_choice.unwrap_or(api_enums::LockerChoice::Basilisk); let jwe_payload = mk_basilisk_req(jwekey, &jws, target_locker).await?; - - let body = utils::Encode::::encode_to_value(&jwe_payload) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = match target_locker { api_enums::LockerChoice::Basilisk => locker.host.to_owned(), api_enums::LockerChoice::Tartarus => locker.host_rs.to_owned(), @@ -475,30 +487,24 @@ pub async fn mk_get_card_request_hs( url.push_str("/cards/retrieve"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(jwe_payload))); Ok(request) } -pub fn mk_get_card_request<'a>( +pub fn mk_get_card_request( locker: &settings::Locker, - locker_id: &'a str, - card_id: &'a str, + locker_id: &'static str, + card_id: &'static str, ) -> CustomResult { let get_card_req = GetCard { merchant_id: locker_id, card_id, }; - let body = utils::Encode::>::url_encode(&get_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/getCard"); let mut request = services::Request::new(services::Method::Post, &url); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(get_card_req))); Ok(request) } @@ -549,37 +555,29 @@ pub async fn mk_delete_card_request_hs( let jwe_payload = mk_basilisk_req(jwekey, &jws, api_enums::LockerChoice::Basilisk).await?; - let body = utils::Encode::::encode_to_value(&jwe_payload) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/cards/delete"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(jwe_payload))); Ok(request) } -pub fn mk_delete_card_request<'a>( +pub fn mk_delete_card_request( locker: &settings::Locker, - merchant_id: &'a str, - card_id: &'a str, + merchant_id: &'static str, + card_id: &'static str, ) -> CustomResult { let delete_card_req = GetCard { merchant_id, card_id, }; - let body = utils::Encode::>::url_encode(&delete_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/deleteCard"); let mut request = services::Request::new(services::Method::Post, &url); request.add_default_headers(); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(delete_card_req))); Ok(request) } @@ -620,14 +618,12 @@ pub fn mk_crud_locker_request( path: &str, req: api::TokenizePayloadEncrypted, ) -> CustomResult { - let body = utils::Encode::::encode_to_value(&req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.basilisk_host.to_owned(); url.push_str(path); let mut request = services::Request::new(services::Method::Post, &url); request.add_default_headers(); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(req))); Ok(request) } diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 5ad78c9d730..063b6968757 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -4,8 +4,6 @@ use common_utils::{ generate_id_with_default_len, }; use error_stack::{report, IntoReport, ResultExt}; -#[cfg(feature = "basilisk")] -use josekit::jwe; use masking::PeekInterface; use router_env::{instrument, tracing}; use scheduler::{types::process_data, utils as process_tracker_utils}; @@ -23,11 +21,7 @@ use crate::{ }, utils::{self, StringExt}, }; -#[cfg(feature = "basilisk")] -use crate::{core::payment_methods::transformers as payment_methods, services, settings}; const VAULT_SERVICE_NAME: &str = "CARD"; -#[cfg(feature = "basilisk")] -const VAULT_VERSION: &str = "0"; pub struct SupplementaryVaultData { pub customer_id: Option, @@ -51,7 +45,10 @@ impl Vaultable for api::Card { card_number: self.card_number.peek().clone(), exp_year: self.card_exp_year.peek().clone(), exp_month: self.card_exp_month.peek().clone(), - name_on_card: Some(self.card_holder_name.peek().clone()), + name_on_card: self + .card_holder_name + .as_ref() + .map(|name| name.peek().clone()), nickname: None, card_last_four: None, card_token: None, @@ -99,7 +96,7 @@ impl Vaultable for api::Card { .attach_printable("Invalid card number format from the mock locker")?, card_exp_month: value1.exp_month.into(), card_exp_year: value1.exp_year.into(), - card_holder_name: value1.name_on_card.unwrap_or_default().into(), + card_holder_name: value1.name_on_card.map(masking::Secret::new), card_cvc: value2.card_security_code.unwrap_or_default().into(), card_issuer: None, card_network: None, @@ -354,7 +351,7 @@ impl Vaultable for api::CardPayout { card_number: self.card_number.peek().clone(), exp_year: self.expiry_year.peek().clone(), exp_month: self.expiry_month.peek().clone(), - name_on_card: Some(self.card_holder_name.peek().clone()), + name_on_card: self.card_holder_name.clone().map(|n| n.peek().to_string()), nickname: None, card_last_four: None, card_token: None, @@ -400,7 +397,7 @@ impl Vaultable for api::CardPayout { .map_err(|_| errors::VaultError::FetchCardFailed)?, expiry_month: value1.exp_month.into(), expiry_year: value1.exp_year.into(), - card_holder_name: value1.name_on_card.unwrap_or_default().into(), + card_holder_name: value1.name_on_card.map(masking::Secret::new), }; let supp_data = SupplementaryVaultData { @@ -424,9 +421,9 @@ pub struct TokenizedBankSensitiveValues { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TokenizedBankInsensitiveValues { pub customer_id: Option, - pub bank_name: String, - pub bank_country_code: api::enums::CountryAlpha2, - pub bank_city: String, + pub bank_name: Option, + pub bank_country_code: Option, + pub bank_city: Option, } #[cfg(feature = "payouts")] @@ -705,7 +702,8 @@ impl Vault { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error getting Value2 for locker")?; - let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token")); + let lookup_key = + token_id.unwrap_or_else(|| generate_id_with_default_len("temporary_token")); let lookup_key = create_tokenize( state, @@ -803,11 +801,6 @@ pub async fn create_tokenize( } Err(err) => { logger::error!("Redis Temp locker Failed: {:?}", err); - - #[cfg(feature = "basilisk")] - return old_create_tokenize(state, value1, value2, lookup_key).await; - - #[cfg(not(feature = "basilisk"))] Err(err) } } @@ -871,11 +864,6 @@ pub async fn get_tokenized_data( } Err(err) => { logger::error!("Redis Temp locker Failed: {:?}", err); - - #[cfg(feature = "basilisk")] - return old_get_tokenized_data(state, lookup_key, _should_get_value2).await; - - #[cfg(not(feature = "basilisk"))] Err(err) } } @@ -922,11 +910,6 @@ pub async fn delete_tokenized_data(state: &routes::AppState, lookup_key: &str) - } Err(err) => { logger::error!("Redis Temp locker Failed: {:?}", err); - - #[cfg(feature = "basilisk")] - return old_delete_tokenized_data(state, lookup_key).await; - - #[cfg(not(feature = "basilisk"))] Err(err) } } @@ -1053,246 +1036,3 @@ pub async fn retry_delete_tokenize( } // Fallback logic of old temp locker needs to be removed later - -#[cfg(feature = "basilisk")] -async fn get_locker_jwe_keys( - keys: &settings::ActiveKmsSecrets, -) -> CustomResult<(String, String), errors::EncryptionError> { - let keys = keys.jwekey.peek(); - let key_id = get_key_id(keys); - let (public_key, private_key) = if key_id == keys.locker_key_identifier1 { - (&keys.locker_encryption_key1, &keys.locker_decryption_key1) - } else if key_id == keys.locker_key_identifier2 { - (&keys.locker_encryption_key2, &keys.locker_decryption_key2) - } else { - return Err(errors::EncryptionError.into()); - }; - - Ok((public_key.to_string(), private_key.to_string())) -} - -#[cfg(feature = "basilisk")] -#[instrument(skip(state, value1, value2))] -pub async fn old_create_tokenize( - state: &routes::AppState, - value1: String, - value2: Option, - lookup_key: String, -) -> RouterResult { - let payload_to_be_encrypted = api::TokenizePayloadRequest { - value1, - value2: value2.unwrap_or_default(), - lookup_key, - service_name: VAULT_SERVICE_NAME.to_string(), - }; - let payload = utils::Encode::::encode_to_string_of_json( - &payload_to_be_encrypted, - ) - .change_context(errors::ApiErrorResponse::InternalServerError)?; - - let (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encryption key")?; - let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: get_key_id(&state.conf.jwekey).to_string(), - version: Some(VAULT_VERSION.to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making tokenize request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - - match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; - let alg = jwe::RSA_OAEP_256; - let decrypted_payload = services::decrypt_jwe( - &resp.payload, - services::KeyIdCheck::RequestResponseKeyId(( - get_key_id(&state.conf.jwekey), - &resp.key_id, - )), - private_key, - alg, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decrypt Jwe failed for TokenizePayloadEncrypted")?; - let get_response: api::GetTokenizePayloadResponse = decrypted_payload - .parse_struct("GetTokenizePayloadResponse") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Error getting GetTokenizePayloadResponse from tokenize response", - )?; - Ok(get_response.lookup_key) - } - Err(err) => { - metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); - Err(errors::ApiErrorResponse::InternalServerError) - .into_report() - .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}")) - } - } -} - -#[cfg(feature = "basilisk")] -pub async fn old_get_tokenized_data( - state: &routes::AppState, - lookup_key: &str, - should_get_value2: bool, -) -> RouterResult { - metrics::GET_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]); - let payload_to_be_encrypted = api::GetTokenizePayloadRequest { - lookup_key: lookup_key.to_string(), - get_value2: should_get_value2, - service_name: VAULT_SERVICE_NAME.to_string(), - }; - let payload = serde_json::to_string(&payload_to_be_encrypted) - .map_err(|_x| errors::ApiErrorResponse::InternalServerError)?; - - let (public_key, private_key) = get_locker_jwe_keys(&state.kms_secrets) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encryption key")?; - let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: get_key_id(&state.conf.jwekey).to_string(), - version: Some("0".to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize/get", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making Get Tokenized request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - match response { - Ok(r) => { - let resp: api::TokenizePayloadEncrypted = r - .response - .parse_struct("TokenizePayloadEncrypted") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for TokenizePayloadEncrypted")?; - let alg = jwe::RSA_OAEP_256; - let decrypted_payload = services::decrypt_jwe( - &resp.payload, - services::KeyIdCheck::RequestResponseKeyId(( - get_key_id(&state.conf.jwekey), - &resp.key_id, - )), - private_key, - alg, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("GetTokenizedApi: Decrypt Jwe failed for TokenizePayloadEncrypted")?; - let get_response: api::TokenizePayloadRequest = decrypted_payload - .parse_struct("TokenizePayloadRequest") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting TokenizePayloadRequest from tokenize response")?; - Ok(get_response) - } - Err(err) => { - metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); - match err.status_code { - 404 => Err(errors::ApiErrorResponse::UnprocessableEntity { - message: "Token is invalid or expired".into(), - } - .into()), - _ => Err(errors::ApiErrorResponse::InternalServerError) - .into_report() - .attach_printable(format!("Got error from the basilisk locker: {err:?}")), - } - } - } -} - -#[cfg(feature = "basilisk")] -pub async fn old_delete_tokenized_data( - state: &routes::AppState, - lookup_key: &str, -) -> RouterResult<()> { - metrics::DELETED_TOKENIZED_CARD.add(&metrics::CONTEXT, 1, &[]); - let payload_to_be_encrypted = api::DeleteTokenizeByTokenRequest { - lookup_key: lookup_key.to_string(), - service_name: VAULT_SERVICE_NAME.to_string(), - }; - let payload = serde_json::to_string(&payload_to_be_encrypted) - .into_report() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error serializing api::DeleteTokenizeByTokenRequest")?; - - let (public_key, _private_key) = get_locker_jwe_keys(&state.kms_secrets.clone()) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encryption key")?; - let encrypted_payload = services::encrypt_jwe(payload.as_bytes(), public_key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error getting Encrypt JWE response")?; - let create_tokenize_request = api::TokenizePayloadEncrypted { - payload: encrypted_payload, - key_id: get_key_id(&state.conf.jwekey).to_string(), - version: Some("0".to_string()), - }; - let request = payment_methods::mk_crud_locker_request( - &state.conf.locker, - "/tokenize/delete/token", - create_tokenize_request, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Making Delete Tokenized request failed")?; - let response = services::call_connector_api(state, request) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while making /tokenize/delete/token call to the locker")?; - match response { - Ok(r) => { - let _delete_response = std::str::from_utf8(&r.response) - .into_report() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Decoding Failed for basilisk delete response")?; - Ok(()) - } - Err(err) => { - metrics::TEMP_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); - Err(errors::ApiErrorResponse::InternalServerError) - .into_report() - .attach_printable(format!("Got 4xx from the basilisk locker: {err:?}")) - } - } -} - -#[cfg(feature = "basilisk")] -pub fn get_key_id(keys: &settings::Jwekey) -> &str { - let key_identifier = "1"; // [#46]: Fetch this value from redis or external sources - if key_identifier == "1" { - &keys.locker_key_identifier1 - } else { - &keys.locker_key_identifier2 - } -} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 0871dccd299..7fa56ae3db2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -44,7 +44,7 @@ use super::{errors::StorageErrorExt, payment_methods::surcharge_decision_configs #[cfg(feature = "frm")] use crate::core::fraud_check as frm_core; use crate::{ - configs::settings::PaymentMethodTypeTokenFilter, + configs::settings::{ApplePayPreDecryptFlow, PaymentMethodTypeTokenFilter}, core::{ errors::{self, CustomResult, RouterResponse, RouterResult}, payment_methods::PaymentMethodRetrieve, @@ -89,7 +89,7 @@ pub async fn payments_operation_core( )> where F: Send + Clone + Sync, - Req: Authenticate, + Req: Authenticate + Clone, Op: Operation + Send + Sync, // To create connector flow specific interface data @@ -166,6 +166,7 @@ where &mut payment_data, &validate_result, &key_store, + &customer, ) .await?; @@ -204,10 +205,6 @@ where ); if should_continue_transaction { - operation - .to_domain()? - .populate_payment_data(state, &mut payment_data, &merchant_account) - .await?; payment_data = match connector_details { api::ConnectorCallType::PreDetermined(connector) => { let schedule_time = if should_add_task_to_process_tracker { @@ -341,6 +338,7 @@ where call_surcharge_decision_management_for_session_flow( state, &merchant_account, + &business_profile, &mut payment_data, &connectors, ) @@ -427,6 +425,23 @@ where .await?; } + let cloned_payment_data = payment_data.clone(); + let cloned_customer = customer.clone(); + let cloned_request = req.clone(); + + crate::utils::trigger_payments_webhook( + merchant_account, + business_profile, + cloned_payment_data, + Some(cloned_request), + cloned_customer, + state, + operation, + ) + .await + .map_err(|error| logger::warn!(payments_outgoing_webhook_error=?error)) + .ok(); + Ok(( payment_data, req, @@ -487,11 +502,17 @@ where .surcharge_applicable .unwrap_or(false) { + if let Some(surcharge_details) = payment_data.payment_attempt.get_surcharge_details() { + // if retry payment, surcharge would have been populated from the previous attempt. Use the same surcharge + let surcharge_details = + types::SurchargeDetails::from((&surcharge_details, &payment_data.payment_attempt)); + payment_data.surcharge_details = Some(surcharge_details); + return Ok(()); + } let raw_card_key = payment_data .payment_method_data .as_ref() - .map(get_key_params_for_surcharge_details) - .transpose()? + .and_then(get_key_params_for_surcharge_details) .map(|(payment_method, payment_method_type, card_network)| { types::SurchargeKey::PaymentMethodData( payment_method, @@ -553,6 +574,7 @@ pub fn get_connector_data( pub async fn call_surcharge_decision_management_for_session_flow( state: &AppState, merchant_account: &domain::MerchantAccount, + business_profile: &diesel_models::business_profile::BusinessProfile, payment_data: &mut PaymentData, session_connector_data: &[api::SessionConnectorData], ) -> RouterResult> @@ -565,6 +587,7 @@ where payment_data.payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount; Ok(Some(api::SessionSurchargeDetails::PreDetermined( types::SurchargeDetails { + original_amount: payment_data.payment_attempt.amount, surcharge: Surcharge::Fixed(surcharge_amount), tax_on_surcharge: None, surcharge_amount, @@ -597,7 +620,7 @@ where .attach_printable("error performing surcharge decision operation")?; surcharge_results - .persist_individual_surcharge_details_in_redis(state, merchant_account) + .persist_individual_surcharge_details_in_redis(state, business_profile) .await?; Ok(if surcharge_results.is_empty_result() { @@ -623,7 +646,7 @@ where F: Send + Clone + Sync, FData: Send + Sync, Op: Operation + Send + Sync + Clone, - Req: Debug + Authenticate, + Req: Debug + Authenticate + Clone, Res: transformers::ToResponse, Op>, // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, @@ -1021,6 +1044,7 @@ where validate_result, &merchant_connector_account, key_store, + customer, ) .await?; @@ -1461,6 +1485,30 @@ where let is_error_in_response = router_data.response.is_err(); // If is_error_in_response is true, should_continue_payment should be false, we should throw the error (router_data, !is_error_in_response) + } else if connector.connector_name == router_types::Connector::Nmi + && !matches!(format!("{operation:?}").as_str(), "CompleteAuthorize") + && router_data.auth_type == storage_enums::AuthenticationType::ThreeDs + { + router_data = router_data.preprocessing_steps(state, connector).await?; + + (router_data, false) + } else if (connector.connector_name == router_types::Connector::Cybersource + || connector.connector_name == router_types::Connector::Bankofamerica) + && is_operation_complete_authorize(&operation) + && router_data.auth_type == storage_enums::AuthenticationType::ThreeDs + { + router_data = router_data.preprocessing_steps(state, connector).await?; + + // Should continue the flow only if no redirection_data is returned else a response with redirection form shall be returned + let should_continue = matches!( + router_data.response, + Ok(router_types::PaymentsResponseData::TransactionResponse { + redirection_data: None, + .. + }) + ) && router_data.status + != common_enums::AttemptStatus::AuthenticationFailed; + (router_data, should_continue) } else { (router_data, should_continue_payment) } @@ -1554,6 +1602,7 @@ fn is_payment_method_tokenization_enabled_for_connector( connector_name: &str, payment_method: &storage::enums::PaymentMethod, payment_method_type: &Option, + apple_pay_flow: &Option, ) -> RouterResult { let connector_tokenization_filter = state.conf.tokenization.0.get(connector_name); @@ -1567,13 +1616,35 @@ fn is_payment_method_tokenization_enabled_for_connector( payment_method_type, connector_filter.payment_method_type.clone(), ) + && is_apple_pay_pre_decrypt_type_connector_tokenization( + payment_method_type, + apple_pay_flow, + connector_filter.apple_pay_pre_decrypt_flow.clone(), + ) }) .unwrap_or(false)) } +fn is_apple_pay_pre_decrypt_type_connector_tokenization( + payment_method_type: &Option, + apple_pay_flow: &Option, + apple_pay_pre_decrypt_flow_filter: Option, +) -> bool { + match (payment_method_type, apple_pay_flow) { + ( + Some(storage::enums::PaymentMethodType::ApplePay), + Some(enums::ApplePayFlow::Simplified), + ) => !matches!( + apple_pay_pre_decrypt_flow_filter, + Some(ApplePayPreDecryptFlow::NetworkTokenization) + ), + _ => true, + } +} + fn decide_apple_pay_flow( payment_method_type: &Option, - merchant_connector_account: &Option, + merchant_connector_account: Option<&helpers::MerchantConnectorAccountType>, ) -> Option { payment_method_type.and_then(|pmt| match pmt { api_models::enums::PaymentMethodType::ApplePay => { @@ -1584,9 +1655,9 @@ fn decide_apple_pay_flow( } fn check_apple_pay_metadata( - merchant_connector_account: &Option, + merchant_connector_account: Option<&helpers::MerchantConnectorAccountType>, ) -> Option { - merchant_connector_account.clone().and_then(|mca| { + merchant_connector_account.and_then(|mca| { let metadata = mca.get_metadata(); metadata.and_then(|apple_pay_metadata| { let parsed_metadata = apple_pay_metadata @@ -1727,6 +1798,7 @@ pub async fn get_connector_tokenization_action_when_confirm_true( validate_result: &operations::ValidateResult<'_>, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<(PaymentData, TokenizationAction)> where F: Send + Clone, @@ -1745,24 +1817,10 @@ where .unwrap_or(false); let payment_data_and_tokenization_action = match connector { - Some(connector_name) if is_mandate => { - if connector_name == *"cybersource" { - let (_operation, payment_method_data) = operation - .to_domain()? - .make_pm_data( - state, - payment_data, - validate_result.storage_scheme, - merchant_key_store, - ) - .await?; - payment_data.payment_method_data = payment_method_data; - } - ( - payment_data.to_owned(), - TokenizationAction::SkipConnectorTokenization, - ) - } + Some(_) if is_mandate => ( + payment_data.to_owned(), + TokenizationAction::SkipConnectorTokenization, + ), Some(connector) if is_operation_confirm(&operation) => { let payment_method = &payment_data .payment_attempt @@ -1770,19 +1828,18 @@ where .get_required_value("payment_method")?; let payment_method_type = &payment_data.payment_attempt.payment_method_type; + let apple_pay_flow = + decide_apple_pay_flow(payment_method_type, Some(merchant_connector_account)); + let is_connector_tokenization_enabled = is_payment_method_tokenization_enabled_for_connector( state, &connector, payment_method, payment_method_type, + &apple_pay_flow, )?; - let apple_pay_flow = decide_apple_pay_flow( - payment_method_type, - &Some(merchant_connector_account.clone()), - ); - add_apple_pay_flow_metrics( &apple_pay_flow, payment_data.payment_attempt.connector.clone(), @@ -1808,6 +1865,7 @@ where payment_data, validate_result.storage_scheme, merchant_key_store, + customer, ) .await?; payment_data.payment_method_data = payment_method_data; @@ -1823,6 +1881,7 @@ where payment_data, validate_result.storage_scheme, merchant_key_store, + customer, ) .await?; @@ -1845,7 +1904,7 @@ where }; (payment_data.to_owned(), connector_tokenization_action) } - Some(_) | None => ( + _ => ( payment_data.to_owned(), TokenizationAction::SkipConnectorTokenization, ), @@ -1860,6 +1919,7 @@ pub async fn tokenize_in_router_when_confirm_false( payment_data: &mut PaymentData, validate_result: &operations::ValidateResult<'_>, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult> where F: Send + Clone, @@ -1874,6 +1934,7 @@ where payment_data, validate_result.storage_scheme, merchant_key_store, + customer, ) .await?; payment_data.payment_method_data = payment_method_data; @@ -1944,6 +2005,7 @@ where pub payment_link_data: Option, pub incremental_authorization_details: Option, pub authorizations: Vec, + pub frm_metadata: Option, } #[derive(Debug, Default, Clone)] @@ -2064,6 +2126,10 @@ pub fn is_operation_confirm(operation: &Op) -> bool { matches!(format!("{operation:?}").as_str(), "PaymentConfirm") } +pub fn is_operation_complete_authorize(operation: &Op) -> bool { + matches!(format!("{operation:?}").as_str(), "CompleteAuthorize") +} + #[cfg(feature = "olap")] pub async fn list_payments( state: AppState, @@ -2146,10 +2212,7 @@ pub async fn apply_filters_on_payments( merchant.storage_scheme, ) .await - .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)? - .into_iter() - .map(|(pi, pa)| (pi, pa)) - .collect(); + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; let data: Vec = list.into_iter().map(ForeignFrom::foreign_from).collect(); diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 81ba48e9831..c9f9d6d87f5 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -147,14 +147,12 @@ impl default_imp_for_complete_authorize!( connector::Aci, connector::Adyen, - connector::Bankofamerica, connector::Bitpay, connector::Boku, connector::Cashtocode, connector::Checkout, connector::Coinbase, connector::Cryptopay, - connector::Cybersource, connector::Dlocal, connector::Fiserv, connector::Forte, @@ -165,13 +163,14 @@ default_imp_for_complete_authorize!( connector::Klarna, connector::Multisafepay, connector::Nexinets, - connector::Nmi, connector::Noon, connector::Opayo, connector::Opennode, connector::Payeezy, connector::Payu, + connector::Placetopay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -246,9 +245,11 @@ default_imp_for_webhook_source_verification!( connector::Payeezy, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -326,9 +327,11 @@ default_imp_for_create_customer!( connector::Paypal, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -395,9 +398,11 @@ default_imp_for_connector_redirect_response!( connector::Opennode, connector::Payeezy, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -455,9 +460,11 @@ default_imp_for_connector_request_id!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -538,9 +545,11 @@ default_imp_for_accept_dispute!( connector::Paypal, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -639,9 +648,11 @@ default_imp_for_file_upload!( connector::Paypal, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -718,9 +729,11 @@ default_imp_for_submit_evidence!( connector::Paypal, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -797,9 +810,11 @@ default_imp_for_defend_dispute!( connector::Paypal, connector::Payme, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -847,7 +862,6 @@ default_imp_for_pre_processing_steps!( connector::Airwallex, connector::Authorizedotnet, connector::Bambora, - connector::Bankofamerica, connector::Bitpay, connector::Bluesnap, connector::Boku, @@ -856,7 +870,6 @@ default_imp_for_pre_processing_steps!( connector::Checkout, connector::Coinbase, connector::Cryptopay, - connector::Cybersource, connector::Dlocal, connector::Iatapay, connector::Fiserv, @@ -868,16 +881,17 @@ default_imp_for_pre_processing_steps!( connector::Mollie, connector::Multisafepay, connector::Nexinets, - connector::Nmi, connector::Noon, connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payeezy, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -937,9 +951,11 @@ default_imp_for_payouts!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1017,9 +1033,11 @@ default_imp_for_payouts_create!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1100,9 +1118,11 @@ default_imp_for_payouts_eligibility!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1180,9 +1200,11 @@ default_imp_for_payouts_fulfill!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1260,9 +1282,11 @@ default_imp_for_payouts_cancel!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1341,9 +1365,11 @@ default_imp_for_payouts_quote!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1422,9 +1448,11 @@ default_imp_for_payouts_recipient!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1502,9 +1530,11 @@ default_imp_for_approve!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1583,9 +1613,11 @@ default_imp_for_reject!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1648,6 +1680,7 @@ default_imp_for_fraud_check!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -1730,6 +1763,7 @@ default_imp_for_frm_sale!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -1812,6 +1846,7 @@ default_imp_for_frm_checkout!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -1894,6 +1929,7 @@ default_imp_for_frm_transaction!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -1976,6 +2012,7 @@ default_imp_for_frm_fulfillment!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -2058,6 +2095,7 @@ default_imp_for_frm_record_return!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, @@ -2137,9 +2175,91 @@ default_imp_for_incremental_authorization!( connector::Payme, connector::Paypal, connector::Payu, + connector::Placetopay, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Riskified, + connector::Signifyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +macro_rules! default_imp_for_revoking_mandates { + ($($path:ident::$connector:ident),*) => { + $( impl api::ConnectorMandateRevoke for $path::$connector {} + impl + services::ConnectorIntegration< + api::MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(feature = "dummy_connector")] +impl api::ConnectorMandateRevoke for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, + > for connector::DummyConnector +{ +} +default_imp_for_revoking_mandates!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Placetopay, connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 4ef23f481a2..07af15a336d 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -76,12 +76,6 @@ impl Feature for types::PaymentsAu .connector .validate_capture_method(self.request.capture_method) .to_payment_failed_response()?; - if self.request.surcharge_details.is_some() { - connector - .connector - .validate_if_surcharge_implemented() - .to_payment_failed_response()?; - } if self.should_proceed_with_authorize() { self.decide_authentication_type(); @@ -418,6 +412,7 @@ impl TryFrom for types::PaymentsPreProcessingData browser_info: data.browser_info, surcharge_details: data.surcharge_details, connector_transaction_id: None, + redirect_response: None, }) } } @@ -437,10 +432,11 @@ impl TryFrom for types::PaymentsPreProcessingData order_details: None, router_return_url: None, webhook_url: None, - complete_authorize_url: None, + complete_authorize_url: data.complete_authorize_url, browser_info: data.browser_info, surcharge_details: None, connector_transaction_id: data.connector_transaction_id, + redirect_response: data.redirect_response, }) } } diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 2d52a145fea..68d0ee8d475 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -203,10 +203,19 @@ pub async fn complete_authorize_preprocessing_steps( ], ); + let mut router_data_request = router_data.request.to_owned(); + + if let Ok(types::PaymentsResponseData::TransactionResponse { + connector_metadata, .. + }) = &resp.response + { + router_data_request.connector_meta = connector_metadata.to_owned(); + }; + let authorize_router_data = payments::helpers::router_data_type_conversion::<_, F, _, _, _, _>( resp.clone(), - router_data.request.to_owned(), + router_data_request, resp.response, ); diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 595a6f5e958..de697e02f78 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -1,6 +1,6 @@ use api_models::payments as payment_types; use async_trait::async_trait; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, Report, ResultExt}; #[cfg(feature = "kms")] use external_services::kms; @@ -15,7 +15,7 @@ use crate::{ routes::{self, metrics}, services, types::{self, api, domain}, - utils::{self, OptionExt}, + utils::OptionExt, }; #[async_trait] @@ -124,13 +124,6 @@ fn build_apple_pay_session_request( apple_pay_merchant_cert: String, apple_pay_merchant_cert_key: String, ) -> RouterResult { - let applepay_session_request = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode ApplePay session request to a string of json")?; - let mut url = state.conf.connectors.applepay.base_url.to_owned(); url.push_str("paymentservices/paymentSession"); @@ -142,7 +135,7 @@ fn build_apple_pay_session_request( headers::CONTENT_TYPE.to_string(), "application/json".to_string().into(), )]) - .body(Some(applepay_session_request)) + .set_body(RequestContent::Json(Box::new(request))) .add_certificate(Some(apple_pay_merchant_cert)) .add_certificate_key(Some(apple_pay_merchant_cert_key)) .build(); @@ -378,13 +371,7 @@ fn get_apple_pay_payment_request( merchant_identifier: &str, ) -> RouterResult { let applepay_payment_request = payment_types::ApplePayPaymentRequest { - country_code: session_data - .country - .to_owned() - .get_required_value("country_code") - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "country_code", - })?, + country_code: session_data.country, currency_code: session_data.currency, total: amount_info, merchant_capabilities: Some(payment_request_data.merchant_capabilities), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4e491964e96..92dc1bf5f4b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -23,7 +23,6 @@ use openssl::{ symm::{decrypt_aead, Cipher}, }; use router_env::{instrument, logger, tracing}; -use time::Duration; use uuid::Uuid; use x509_parser::parse_x509_certificate; @@ -953,7 +952,7 @@ pub fn payment_attempt_status_fsm( ) -> storage_enums::AttemptStatus { match payment_method_data { Some(_) => match confirm { - Some(true) => storage_enums::AttemptStatus::Pending, + Some(true) => storage_enums::AttemptStatus::PaymentMethodAwaited, _ => storage_enums::AttemptStatus::ConfirmationAwaited, }, None => storage_enums::AttemptStatus::PaymentMethodAwaited, @@ -966,7 +965,7 @@ pub fn payment_intent_status_fsm( ) -> storage_enums::IntentStatus { match payment_method_data { Some(_) => match confirm { - Some(true) => storage_enums::IntentStatus::RequiresCustomerAction, + Some(true) => storage_enums::IntentStatus::RequiresPaymentMethod, _ => storage_enums::IntentStatus::RequiresConfirmation, }, None => storage_enums::IntentStatus::RequiresPaymentMethod, @@ -1033,7 +1032,7 @@ pub(crate) async fn get_payment_method_create_request( card_number: card.card_number.clone(), card_exp_month: card.card_exp_month.clone(), card_exp_year: card.card_exp_year.clone(), - card_holder_name: Some(card.card_holder_name.clone()), + card_holder_name: card.card_holder_name.clone(), nick_name: card.nick_name.clone(), }; let customer_id = customer.customer_id.clone(); @@ -1042,6 +1041,7 @@ pub(crate) async fn get_payment_method_create_request( payment_method_type, payment_method_issuer: card.card_issuer.clone(), payment_method_issuer_code: None, + bank_transfer: None, card: Some(card_detail), metadata: None, customer_id: Some(customer_id), @@ -1058,6 +1058,7 @@ pub(crate) async fn get_payment_method_create_request( payment_method_type, payment_method_issuer: None, payment_method_issuer_code: None, + bank_transfer: None, card: None, metadata: None, customer_id: Some(customer.customer_id.to_owned()), @@ -1404,21 +1405,27 @@ pub async fn retrieve_payment_method_with_temporary_token( let mut updated_card = card.clone(); let mut is_card_updated = false; - let name_on_card = if card.card_holder_name.clone().expose().is_empty() { - card_token_data - .and_then(|token_data| token_data.card_holder_name.clone()) - .filter(|name_on_card| !name_on_card.clone().expose().is_empty()) - .map(|name_on_card| { - is_card_updated = true; - name_on_card - }) + // The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked + // from payment_method_data.card_token object + let name_on_card = if let Some(name) = card.card_holder_name.clone() { + if name.clone().expose().is_empty() { + card_token_data + .and_then(|token_data| { + is_card_updated = true; + token_data.card_holder_name.clone() + }) + .or(Some(name)) + } else { + card.card_holder_name.clone() + } } else { - Some(card.card_holder_name.clone()) + card_token_data.and_then(|token_data| { + is_card_updated = true; + token_data.card_holder_name.clone() + }) }; - if let Some(name_on_card) = name_on_card { - updated_card.card_holder_name = name_on_card; - } + updated_card.card_holder_name = name_on_card; if let Some(token_data) = card_token_data { if let Some(cvc) = token_data.card_cvc.clone() { @@ -1487,23 +1494,23 @@ pub async fn retrieve_card_with_permanent_token( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("failed to fetch card information from the permanent locker")?; - let name_on_card = if let Some(name_on_card) = card.name_on_card.clone() { - if card.name_on_card.unwrap_or_default().expose().is_empty() { + // The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked + // from payment_method_data.card_token object + let name_on_card = if let Some(name) = card.name_on_card.clone() { + if name.clone().expose().is_empty() { card_token_data .and_then(|token_data| token_data.card_holder_name.clone()) - .filter(|name_on_card| !name_on_card.clone().expose().is_empty()) + .or(Some(name)) } else { - Some(name_on_card) + card.name_on_card } } else { - card_token_data - .and_then(|token_data| token_data.card_holder_name.clone()) - .filter(|name_on_card| !name_on_card.clone().expose().is_empty()) + card_token_data.and_then(|token_data| token_data.card_holder_name.clone()) }; let api_card = api::Card { card_number: card.card_number, - card_holder_name: name_on_card.unwrap_or(masking::Secret::from("".to_string())), + card_holder_name: name_on_card, card_exp_month: card.card_exp_month, card_exp_year: card.card_exp_year, card_cvc: card_token_data @@ -1527,6 +1534,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( state: &'a AppState, payment_data: &mut PaymentData, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, R, Ctx>, Option, @@ -1616,6 +1624,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( &hyperswitch_token, &payment_data.payment_intent, card_token_data.as_ref(), + customer, ) .await .attach_printable("in 'make_pm_data'")?; @@ -2371,24 +2380,23 @@ pub fn generate_mandate( pub fn authenticate_client_secret( request_client_secret: Option<&String>, payment_intent: &PaymentIntent, - merchant_intent_fulfillment_time: Option, ) -> Result<(), errors::ApiErrorResponse> { match (request_client_secret, &payment_intent.client_secret) { (Some(req_cs), Some(pi_cs)) => { if req_cs != pi_cs { Err(errors::ApiErrorResponse::ClientSecretInvalid) } else { - //This is done to check whether the merchant_account's intent fulfillment time has expired or not - let payment_intent_fulfillment_deadline = - payment_intent.created_at.saturating_add(Duration::seconds( - merchant_intent_fulfillment_time - .unwrap_or(consts::DEFAULT_FULFILLMENT_TIME), - )); let current_timestamp = common_utils::date_time::now(); - fp_utils::when( - current_timestamp > payment_intent_fulfillment_deadline, - || Err(errors::ApiErrorResponse::ClientSecretExpired), - ) + + let session_expiry = payment_intent.session_expiry.unwrap_or( + payment_intent + .created_at + .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), + ); + + fp_utils::when(current_timestamp > session_expiry, || { + Err(errors::ApiErrorResponse::ClientSecretExpired) + }) } } // If there is no client in payment intent, then it has expired @@ -2397,26 +2405,6 @@ pub fn authenticate_client_secret( } } -pub async fn get_merchant_fullfillment_time( - payment_link_id: Option, - intent_fulfillment_time: Option, - db: &dyn StorageInterface, -) -> RouterResult> { - if let Some(payment_link_id) = payment_link_id { - let payment_link_db = db - .find_payment_link_by_payment_link_id(&payment_link_id) - .await - .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; - - let curr_time = common_utils::date_time::now(); - Ok(payment_link_db - .fulfilment_time - .map(|merchant_expiry_time| (merchant_expiry_time - curr_time).whole_seconds())) - } else { - Ok(intent_fulfillment_time) - } -} - pub(crate) fn validate_payment_status_against_allowed_statuses( intent_status: &storage_enums::IntentStatus, allowed_statuses: &[storage_enums::IntentStatus], @@ -2489,14 +2477,7 @@ pub async fn verify_payment_intent_time_and_client_secret( .await .change_context(errors::ApiErrorResponse::PaymentNotFound)?; - let intent_fulfillment_time = get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - - authenticate_client_secret(Some(&cs), &payment_intent, intent_fulfillment_time)?; + authenticate_client_secret(Some(&cs), &payment_intent)?; Ok(payment_intent) }) .await @@ -2607,6 +2588,7 @@ mod tests { modified_at: common_utils::date_time::now(), last_synced: None, setup_future_usage: None, + fingerprint_id: None, off_session: None, client_secret: Some("1".to_string()), active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()), @@ -2623,19 +2605,19 @@ mod tests { payment_confirm_source: None, surcharge_applicable: None, updated_by: storage_enums::MerchantStorageScheme::PostgresOnly.to_string(), - request_incremental_authorization: + request_incremental_authorization: Some( common_enums::RequestIncrementalAuthorization::default(), + ), incremental_authorization_allowed: None, authorization_count: None, + session_expiry: Some( + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), + ), }; let req_cs = Some("1".to_string()); - let merchant_fulfillment_time = Some(900); - assert!(authenticate_client_secret( - req_cs.as_ref(), - &payment_intent, - merchant_fulfillment_time, - ) - .is_ok()); // Check if the result is an Ok variant + assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_ok()); + // Check if the result is an Ok variant } #[test] @@ -2657,8 +2639,9 @@ mod tests { billing_address_id: None, statement_descriptor_name: None, statement_descriptor_suffix: None, - created_at: common_utils::date_time::now().saturating_sub(Duration::seconds(20)), + created_at: common_utils::date_time::now().saturating_sub(time::Duration::seconds(20)), modified_at: common_utils::date_time::now(), + fingerprint_id: None, last_synced: None, setup_future_usage: None, off_session: None, @@ -2677,19 +2660,18 @@ mod tests { payment_confirm_source: None, surcharge_applicable: None, updated_by: storage_enums::MerchantStorageScheme::PostgresOnly.to_string(), - request_incremental_authorization: + request_incremental_authorization: Some( common_enums::RequestIncrementalAuthorization::default(), + ), incremental_authorization_allowed: None, authorization_count: None, + session_expiry: Some( + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), + ), }; let req_cs = Some("1".to_string()); - let merchant_fulfillment_time = Some(10); - assert!(authenticate_client_secret( - req_cs.as_ref(), - &payment_intent, - merchant_fulfillment_time, - ) - .is_err()) + assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent,).is_err()) } #[test] @@ -2711,12 +2693,13 @@ mod tests { billing_address_id: None, statement_descriptor_name: None, statement_descriptor_suffix: None, - created_at: common_utils::date_time::now().saturating_sub(Duration::seconds(20)), + created_at: common_utils::date_time::now().saturating_sub(time::Duration::seconds(20)), modified_at: common_utils::date_time::now(), last_synced: None, setup_future_usage: None, off_session: None, client_secret: None, + fingerprint_id: None, active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()), business_country: None, business_label: None, @@ -2731,19 +2714,18 @@ mod tests { payment_confirm_source: None, surcharge_applicable: None, updated_by: storage_enums::MerchantStorageScheme::PostgresOnly.to_string(), - request_incremental_authorization: + request_incremental_authorization: Some( common_enums::RequestIncrementalAuthorization::default(), + ), incremental_authorization_allowed: None, authorization_count: None, + session_expiry: Some( + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), + ), }; let req_cs = Some("1".to_string()); - let merchant_fulfillment_time = Some(10); - assert!(authenticate_client_secret( - req_cs.as_ref(), - &payment_intent, - merchant_fulfillment_time, - ) - .is_err()) + assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_err()) } } @@ -2958,6 +2940,7 @@ pub fn router_data_type_conversion( connector_http_status_code: router_data.connector_http_status_code, external_latency: router_data.external_latency, apple_pay_flow: router_data.apple_pay_flow, + frm_metadata: router_data.frm_metadata, } } @@ -3113,8 +3096,8 @@ impl AttemptType { error_message: None, offer_amount: old_payment_attempt.offer_amount, - surcharge_amount: old_payment_attempt.surcharge_amount, - tax_amount: old_payment_attempt.tax_amount, + surcharge_amount: None, + tax_amount: None, payment_method_id: None, payment_method: None, capture_method: old_payment_attempt.capture_method, @@ -3157,6 +3140,7 @@ impl AttemptType { merchant_connector_id: None, unified_code: None, unified_message: None, + net_amount: old_payment_attempt.amount, } } @@ -3308,6 +3292,7 @@ pub async fn get_additional_payment_data( match pm_data { api_models::payments::PaymentMethodData::Card(card_data) => { let card_isin = Some(card_data.card_number.clone().get_card_isin()); + let card_extended_bin = Some(card_data.card_number.clone().get_card_extended_bin()); let last4 = Some(card_data.card_number.clone().get_last4()); if card_data.card_issuer.is_some() && card_data.card_network.is_some() @@ -3324,9 +3309,10 @@ pub async fn get_additional_payment_data( bank_code: card_data.bank_code.to_owned(), card_exp_month: Some(card_data.card_exp_month.clone()), card_exp_year: Some(card_data.card_exp_year.clone()), - card_holder_name: Some(card_data.card_holder_name.clone()), + card_holder_name: card_data.card_holder_name.clone(), last4: last4.clone(), card_isin: card_isin.clone(), + card_extended_bin: card_extended_bin.clone(), }, )) } else { @@ -3350,9 +3336,10 @@ pub async fn get_additional_payment_data( card_issuing_country: card_info.card_issuing_country, last4: last4.clone(), card_isin: card_isin.clone(), + card_extended_bin: card_extended_bin.clone(), card_exp_month: Some(card_data.card_exp_month.clone()), card_exp_year: Some(card_data.card_exp_year.clone()), - card_holder_name: Some(card_data.card_holder_name.clone()), + card_holder_name: card_data.card_holder_name.clone(), }, )) }); @@ -3365,9 +3352,10 @@ pub async fn get_additional_payment_data( card_issuing_country: None, last4, card_isin, + card_extended_bin, card_exp_month: Some(card_data.card_exp_month.clone()), card_exp_year: Some(card_data.card_exp_year.clone()), - card_holder_name: Some(card_data.card_holder_name.clone()), + card_holder_name: card_data.card_holder_name.clone(), }, ))) } @@ -3611,8 +3599,12 @@ impl ApplePayData { .into_report() .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; let iv = [0u8; 16]; //Initialization vector IV is typically used in AES-GCM (Galois/Counter Mode) encryption for randomizing the encryption process. - let ciphertext = &data[..data.len() - 16]; - let tag = &data[data.len() - 16..]; + let ciphertext = data + .get(..data.len() - 16) + .ok_or(errors::ApplePayDecryptionError::DecryptionFailed)?; + let tag = data + .get(data.len() - 16..) + .ok_or(errors::ApplePayDecryptionError::DecryptionFailed)?; let cipher = Cipher::aes_256_gcm(); let decrypted_data = decrypt_aead(cipher, symmetric_key, Some(&iv), &[], ciphertext, tag) .into_report() @@ -3627,113 +3619,83 @@ impl ApplePayData { pub fn get_key_params_for_surcharge_details( payment_method_data: &api_models::payments::PaymentMethodData, -) -> RouterResult<( +) -> Option<( common_enums::PaymentMethod, common_enums::PaymentMethodType, Option, )> { match payment_method_data { api_models::payments::PaymentMethodData::Card(card) => { - let card_network = card - .card_network - .clone() - .get_required_value("payment_method_data.card.card_network")?; // surcharge generated will always be same for credit as well as debit // since surcharge conditions cannot be defined on card_type - Ok(( + Some(( common_enums::PaymentMethod::Card, common_enums::PaymentMethodType::Credit, - Some(card_network), + card.card_network.clone(), )) } - api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok(( + api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Some(( common_enums::PaymentMethod::CardRedirect, card_redirect_data.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::Wallet(wallet) => Ok(( + api_models::payments::PaymentMethodData::Wallet(wallet) => Some(( common_enums::PaymentMethod::Wallet, wallet.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::PayLater(pay_later) => Ok(( + api_models::payments::PaymentMethodData::PayLater(pay_later) => Some(( common_enums::PaymentMethod::PayLater, pay_later.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::BankRedirect(bank_redirect) => Ok(( + api_models::payments::PaymentMethodData::BankRedirect(bank_redirect) => Some(( common_enums::PaymentMethod::BankRedirect, bank_redirect.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::BankDebit(bank_debit) => Ok(( + api_models::payments::PaymentMethodData::BankDebit(bank_debit) => Some(( common_enums::PaymentMethod::BankDebit, bank_debit.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::BankTransfer(bank_transfer) => Ok(( + api_models::payments::PaymentMethodData::BankTransfer(bank_transfer) => Some(( common_enums::PaymentMethod::BankTransfer, bank_transfer.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::Crypto(crypto) => Ok(( + api_models::payments::PaymentMethodData::Crypto(crypto) => Some(( common_enums::PaymentMethod::Crypto, crypto.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::MandatePayment => { - Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_method_data", - } - .into()) - } - api_models::payments::PaymentMethodData::Reward => { - Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_method_data", - } - .into()) - } - api_models::payments::PaymentMethodData::Upi(_) => Ok(( + api_models::payments::PaymentMethodData::MandatePayment => None, + api_models::payments::PaymentMethodData::Reward => None, + api_models::payments::PaymentMethodData::Upi(_) => Some(( common_enums::PaymentMethod::Upi, common_enums::PaymentMethodType::UpiCollect, None, )), - api_models::payments::PaymentMethodData::Voucher(voucher) => Ok(( + api_models::payments::PaymentMethodData::Voucher(voucher) => Some(( common_enums::PaymentMethod::Voucher, voucher.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::GiftCard(gift_card) => Ok(( + api_models::payments::PaymentMethodData::GiftCard(gift_card) => Some(( common_enums::PaymentMethod::GiftCard, gift_card.get_payment_method_type(), None, )), - api_models::payments::PaymentMethodData::CardToken(_) => { - Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_method_data", - } - .into()) - } + api_models::payments::PaymentMethodData::CardToken(_) => None, } } pub fn validate_payment_link_request( - payment_link_object: &api_models::payments::PaymentLinkObject, confirm: Option, - order_details: Option>, ) -> Result<(), errors::ApiErrorResponse> { if let Some(cnf) = confirm { if !cnf { - let current_time = Some(common_utils::date_time::now()); - if current_time > payment_link_object.link_expiry { - return Err(errors::ApiErrorResponse::InvalidRequestData { - message: "link_expiry time cannot be less than current time".to_string(), - }); - } else if order_details.is_none() { - return Err(errors::ApiErrorResponse::InvalidRequestData { - message: "cannot create payment link without order details".to_string(), - }); - } + return Ok(()); } else { return Err(errors::ApiErrorResponse::InvalidRequestData { message: "cannot confirm a payment while creating a payment link".to_string(), @@ -3789,16 +3751,32 @@ pub async fn get_gsm_record( pub fn validate_order_details_amount( order_details: Vec, amount: i64, + should_validate: bool, ) -> Result<(), errors::ApiErrorResponse> { - let total_order_details_amount: i64 = order_details - .iter() - .map(|order| order.amount * i64::from(order.quantity)) - .sum(); + if should_validate { + 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(()) + } + } else { + Ok(()) + } +} - if total_order_details_amount != amount { +// This function validates the client secret expiry set by the merchant in the request +pub fn validate_session_expiry(session_expiry: u32) -> Result<(), errors::ApiErrorResponse> { + if !(consts::MIN_SESSION_EXPIRY..=consts::MAX_SESSION_EXPIRY).contains(&session_expiry) { Err(errors::ApiErrorResponse::InvalidRequestData { - message: "Total sum of order details doesn't match amount in payment request" - .to_string(), + message: "session_expiry should be between 60(1 min) to 7890000(3 months).".to_string(), }) } else { Ok(()) diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index cf0c0ab294a..716b76120d8 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -131,6 +131,7 @@ pub trait Domain: Send + Sync { payment_data: &mut PaymentData, storage_scheme: enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, R, Ctx>, Option, @@ -251,11 +252,19 @@ where payment_data: &mut PaymentData, _storage_scheme: enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRetrieveRequest, Ctx>, Option, )> { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await + helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await } } @@ -301,6 +310,7 @@ where _payment_data: &mut PaymentData, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCaptureRequest, Ctx>, Option, @@ -363,6 +373,7 @@ where _payment_data: &mut PaymentData, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsCancelRequest, Ctx>, Option, @@ -415,6 +426,7 @@ where _payment_data: &mut PaymentData, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRejectRequest, Ctx>, Option, diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 37a3e1a1412..cddbc89acff 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -139,7 +139,7 @@ impl payment_method_type.or(payment_attempt.payment_method_type); payment_attempt.payment_experience = request.payment_experience; currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); helpers::validate_customer_id_mandatory_cases( request.setup_future_usage.is_some(), @@ -257,6 +257,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let customer_details = Some(CustomerDetails { @@ -314,12 +315,19 @@ impl Domain, _storage_scheme: storage_enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { - let (op, payment_method_data) = - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await?; + let (op, payment_method_data) = helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await?; utils::when(payment_method_data.is_none(), || { Err(errors::ApiErrorResponse::PaymentMethodNotFound) diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 7c8fbcc3497..9810980cd34 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -102,7 +102,7 @@ impl .await?; let currency = payment_attempt.currency.get_required_value("currency")?; - let amount = payment_attempt.amount.into(); + let amount = payment_attempt.get_total_amount().into(); payment_attempt.cancellation_reason = request.cancellation_reason.clone(); @@ -173,6 +173,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 65b91f0401c..3986b16ce35 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -124,7 +124,7 @@ impl currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, @@ -217,6 +217,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 48b503b96b0..adc137403e5 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -135,7 +135,7 @@ impl .payment_experience .or(payment_attempt.payment_experience); currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); helpers::validate_customer_id_mandatory_cases( request.setup_future_usage.is_some(), @@ -253,6 +253,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let customer_details = Some(CustomerDetails { @@ -310,12 +311,19 @@ impl Domain, _storage_scheme: storage_enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { - let (op, payment_method_data) = - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await?; + let (op, payment_method_data) = helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await?; Ok((op, payment_method_data)) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index af2a9fa49c8..c81145c5de7 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -2,23 +2,30 @@ use std::marker::PhantomData; use api_models::enums::FrmSuggestion; use async_trait::async_trait; -use common_utils::ext_traits::{AsyncExt, Encode}; +use common_utils::{ + crypto::{self, SignMessage}, + ext_traits::{AsyncExt, Encode}, +}; use error_stack::{report, IntoReport, ResultExt}; +#[cfg(feature = "kms")] +use external_services::kms; use futures::FutureExt; use router_derive::PaymentOperation; -use router_env::{instrument, tracing}; +use router_env::{instrument, logger, tracing}; use tracing_futures::Instrument; use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; use crate::{ + consts, core::{ + blocklist::utils as blocklist_utils, errors::{self, CustomResult, RouterResult, StorageErrorExt}, payment_methods::PaymentMethodRetrieve, payments::{ self, helpers, operations, populate_surcharge_details, CustomerDetails, PaymentAddress, PaymentData, }, - utils::{self as core_utils}, + utils as core_utils, }, db::StorageInterface, routes::AppState, @@ -50,7 +57,6 @@ impl key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, ) -> RouterResult> { - let db = &*state.store; let merchant_id = &merchant_account.merchant_id; let storage_scheme = merchant_account.storage_scheme; let (currency, amount); @@ -105,6 +111,7 @@ impl helpers::validate_order_details_amount( order_details.to_owned(), payment_intent.amount, + false, )?; } @@ -122,18 +129,7 @@ impl "confirm", )?; - let intent_fulfillment_time = helpers::get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - - helpers::authenticate_client_secret( - request.client_secret.as_ref(), - &payment_intent, - intent_fulfillment_time, - )?; + helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?; let customer_details = helpers::get_customer_details_from_request(request); @@ -161,7 +157,8 @@ impl .await }); - let store = state.clone().store; + let store = state.store.clone(); + let m_payment_id = payment_intent.payment_id.clone(); let m_merchant_id = merchant_id.clone(); @@ -377,7 +374,7 @@ impl payment_attempt.capture_method = request.capture_method.or(payment_attempt.capture_method); currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); helpers::validate_customer_id_mandatory_cases( request.setup_future_usage.is_some(), @@ -488,6 +485,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { @@ -537,12 +535,13 @@ impl Domain, _storage_scheme: storage_enums::MerchantStorageScheme, key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { let (op, payment_method_data) = - helpers::make_pm_data(Box::new(self), state, payment_data, key_store).await?; + helpers::make_pm_data(Box::new(self), state, payment_data, key_store, customer).await?; utils::when(payment_method_data.is_none(), || { Err(errors::ApiErrorResponse::PaymentMethodNotFound) @@ -628,32 +627,34 @@ impl where F: 'b + Send, { + let db = state.store.as_ref(); let payment_method = payment_data.payment_attempt.payment_method; let browser_info = payment_data.payment_attempt.browser_info.clone(); let frm_message = payment_data.frm_message.clone(); - let (intent_status, attempt_status, (error_code, error_message)) = match frm_suggestion { - Some(FrmSuggestion::FrmCancelTransaction) => ( - storage_enums::IntentStatus::Failed, - storage_enums::AttemptStatus::Failure, - frm_message.map_or((None, None), |fraud_check| { - ( - Some(Some(fraud_check.frm_status.to_string())), - Some(fraud_check.frm_reason.map(|reason| reason.to_string())), - ) - }), - ), - Some(FrmSuggestion::FrmManualReview) => ( - storage_enums::IntentStatus::RequiresMerchantAction, - storage_enums::AttemptStatus::Unresolved, - (None, None), - ), - _ => ( - storage_enums::IntentStatus::Processing, - storage_enums::AttemptStatus::Pending, - (None, None), - ), - }; + let (mut intent_status, mut attempt_status, (error_code, error_message)) = + match frm_suggestion { + Some(FrmSuggestion::FrmCancelTransaction) => ( + storage_enums::IntentStatus::Failed, + storage_enums::AttemptStatus::Failure, + frm_message.map_or((None, None), |fraud_check| { + ( + Some(Some(fraud_check.frm_status.to_string())), + Some(fraud_check.frm_reason.map(|reason| reason.to_string())), + ) + }), + ), + Some(FrmSuggestion::FrmManualReview) => ( + storage_enums::IntentStatus::RequiresMerchantAction, + storage_enums::AttemptStatus::Unresolved, + (None, None), + ), + _ => ( + storage_enums::IntentStatus::Processing, + storage_enums::AttemptStatus::Pending, + (None, None), + ), + }; let connector = payment_data.payment_attempt.connector.clone(); let merchant_connector_id = payment_data.payment_attempt.merchant_connector_id.clone(); @@ -717,6 +718,157 @@ impl let m_error_message = error_message.clone(); let m_db = state.clone().store; + // Validate Blocklist + let merchant_id = payment_data.payment_attempt.merchant_id; + let merchant_fingerprint_secret = + blocklist_utils::get_merchant_fingerprint_secret(state, &merchant_id).await?; + + // Hashed Fingerprint to check whether or not this payment should be blocked. + let card_number_fingerprint = payment_data + .payment_method_data + .as_ref() + .and_then(|pm_data| match pm_data { + api_models::payments::PaymentMethodData::Card(card) => { + crypto::HmacSha512::sign_message( + &crypto::HmacSha512, + merchant_fingerprint_secret.as_bytes(), + card.card_number.clone().get_card_no().as_bytes(), + ) + .attach_printable("error in pm fingerprint creation") + .map_or_else( + |err| { + logger::error!(error=?err); + None + }, + Some, + ) + } + _ => None, + }) + .map(hex::encode); + + // Hashed Cardbin to check whether or not this payment should be blocked. + let card_bin_fingerprint = payment_data + .payment_method_data + .as_ref() + .and_then(|pm_data| match pm_data { + api_models::payments::PaymentMethodData::Card(card) => { + crypto::HmacSha512::sign_message( + &crypto::HmacSha512, + merchant_fingerprint_secret.as_bytes(), + card.card_number.clone().get_card_isin().as_bytes(), + ) + .attach_printable("error in card bin hash creation") + .map_or_else( + |err| { + logger::error!(error=?err); + None + }, + Some, + ) + } + _ => None, + }) + .map(hex::encode); + + // Hashed Extended Cardbin to check whether or not this payment should be blocked. + let extended_card_bin_fingerprint = payment_data + .payment_method_data + .as_ref() + .and_then(|pm_data| match pm_data { + api_models::payments::PaymentMethodData::Card(card) => { + crypto::HmacSha512::sign_message( + &crypto::HmacSha512, + merchant_fingerprint_secret.as_bytes(), + card.card_number.clone().get_extended_card_bin().as_bytes(), + ) + .attach_printable("error in extended card bin hash creation") + .map_or_else( + |err| { + logger::error!(error=?err); + None + }, + Some, + ) + } + _ => None, + }) + .map(hex::encode); + + let mut fingerprint_id = None; + + //validating the payment method. + let mut is_pm_blocklisted = false; + + let mut blocklist_futures = Vec::new(); + if let Some(card_number_fingerprint) = card_number_fingerprint.as_ref() { + blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &merchant_id, + card_number_fingerprint, + )); + } + + if let Some(card_bin_fingerprint) = card_bin_fingerprint.as_ref() { + blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &merchant_id, + card_bin_fingerprint, + )); + } + + if let Some(extended_card_bin_fingerprint) = extended_card_bin_fingerprint.as_ref() { + blocklist_futures.push(db.find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &merchant_id, + extended_card_bin_fingerprint, + )); + } + + let blocklist_lookups = futures::future::join_all(blocklist_futures).await; + + if blocklist_lookups.iter().any(|x| x.is_ok()) { + intent_status = storage_enums::IntentStatus::Failed; + attempt_status = storage_enums::AttemptStatus::Failure; + is_pm_blocklisted = true; + } + + if let Some(encoded_hash) = card_number_fingerprint { + #[cfg(feature = "kms")] + let encrypted_fingerprint = kms::get_kms_client(&state.conf.kms) + .await + .encrypt(encoded_hash) + .await + .map_or_else( + |e| { + logger::error!(error=?e, "failed kms encryption of card fingerprint"); + None + }, + Some, + ); + + #[cfg(not(feature = "kms"))] + let encrypted_fingerprint = Some(encoded_hash); + + if let Some(encrypted_fingerprint) = encrypted_fingerprint { + fingerprint_id = db + .insert_blocklist_fingerprint_entry( + diesel_models::blocklist_fingerprint::BlocklistFingerprintNew { + merchant_id, + fingerprint_id: utils::generate_id(consts::ID_LENGTH, "fingerprint"), + encrypted_fingerprint, + data_kind: common_enums::BlocklistDataKind::PaymentMethod, + created_at: common_utils::date_time::now(), + }, + ) + .await + .map_or_else( + |e| { + logger::error!(error=?e, "failed storing card fingerprint in db"); + None + }, + |fp| Some(fp.fingerprint_id), + ); + } + } + let surcharge_amount = payment_data .surcharge_details .as_ref() @@ -731,7 +883,7 @@ impl m_db.update_payment_attempt_with_attempt_id( m_payment_data_payment_attempt, storage::PaymentAttemptUpdate::ConfirmUpdate { - amount: payment_data.amount.into(), + amount: payment_data.payment_attempt.amount, currency: payment_data.currency, status: attempt_status, payment_method, @@ -773,13 +925,14 @@ impl let m_metadata = metadata.clone(); let m_db = state.clone().store; let m_storage_scheme = storage_scheme.to_string(); + let session_expiry = m_payment_data_payment_intent.session_expiry; let payment_intent_fut = tokio::spawn( async move { m_db.update_payment_intent( m_payment_data_payment_intent, storage::PaymentIntentUpdate::Update { - amount: payment_data.amount.into(), + amount: payment_data.payment_intent.amount, currency: payment_data.currency, setup_future_usage, status: intent_status, @@ -796,6 +949,8 @@ impl metadata: m_metadata, payment_confirm_source: header_payload.payment_confirm_source, updated_by: m_storage_scheme, + fingerprint_id, + session_expiry, }, storage_scheme, ) @@ -844,6 +999,11 @@ impl payment_data.payment_intent = payment_intent; payment_data.payment_attempt = payment_attempt; + // Block the payment if the entry was present in the Blocklist + if is_pm_blocklisted { + return Err(errors::ApiErrorResponse::PaymentBlocked.into()); + } + Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index eb7f31ba24d..2b25a74deb1 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -6,14 +6,17 @@ use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; use data_models::{mandates::MandateData, payments::payment_attempt::PaymentAttempt}; use diesel_models::ephemeral_key; use error_stack::{self, ResultExt}; +use masking::PeekInterface; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; +use time::PrimitiveDateTime; use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; use crate::{ consts, core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, + payment_link, payment_methods::PaymentMethodRetrieve, payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData}, utils as core_utils, @@ -66,31 +69,36 @@ impl .get_payment_intent_id() .change_context(errors::ApiErrorResponse::PaymentNotFound)?; - let payment_link_data = if let Some(payment_link_object) = &request.payment_link_object { - create_payment_link( - request, - payment_link_object.clone(), - merchant_id.clone(), - payment_id.clone(), - db, - state, - amount, - request.description.clone(), - ) - .await? - } else { - None - }; - helpers::validate_business_details( request.business_country, request.business_label.as_ref(), merchant_account, )?; + // If profile id is not passed, get it from the business_country and business_label + let profile_id = core_utils::get_profile_id_from_business_details( + request.business_country, + request.business_label.as_ref(), + merchant_account, + request.profile_id.as_ref(), + &*state.store, + true, + ) + .await?; + // Validate whether profile_id passed in request is valid and is linked to the merchant - core_utils::validate_and_get_business_profile(db, request.profile_id.as_ref(), merchant_id) - .await?; + let business_profile = if let Some(business_profile) = + core_utils::validate_and_get_business_profile(db, Some(&profile_id), merchant_id) + .await? + { + business_profile + } else { + db.find_business_profile_by_profile_id(&profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.to_string(), + })? + }; let ( token, @@ -154,6 +162,52 @@ impl utils::get_payment_attempt_id(payment_id.clone(), 1) }; + let session_expiry = + common_utils::date_time::now().saturating_add(time::Duration::seconds( + request.session_expiry.map(i64::from).unwrap_or( + business_profile + .session_expiry + .unwrap_or(consts::DEFAULT_SESSION_EXPIRY), + ), + )); + + let payment_link_data = if let Some(payment_link_create) = request.payment_link { + if payment_link_create { + let merchant_name = merchant_account + .merchant_name + .clone() + .map(|merchant_name| merchant_name.into_inner().peek().to_owned()) + .unwrap_or_default(); + + let default_domain_name = state.conf.server.base_url.clone(); + + let (payment_link_config, domain_name) = + payment_link::get_payment_link_config_based_on_priority( + request.payment_link_config.clone(), + business_profile.payment_link_config.clone(), + merchant_name, + default_domain_name, + )?; + create_payment_link( + request, + payment_link_config, + merchant_id.clone(), + payment_id.clone(), + db, + amount, + request.description.clone(), + profile_id.clone(), + domain_name, + session_expiry, + ) + .await? + } else { + None + } + } else { + None + }; + let payment_intent_new = Self::make_payment_intent( &payment_id, merchant_account, @@ -163,7 +217,8 @@ impl payment_link_data.clone(), billing_address.clone().map(|x| x.address_id), attempt_id, - state, + profile_id, + session_expiry, ) .await?; @@ -190,6 +245,7 @@ impl helpers::validate_order_details_amount( order_details.to_owned(), payment_intent.amount, + false, )?; } @@ -200,20 +256,6 @@ impl payment_id: payment_id.clone(), })?; - let profile_id = payment_intent - .profile_id - .as_ref() - .get_required_value("profile_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("'profile_id' not set in payment intent")?; - - let business_profile = db - .find_business_profile_by_profile_id(profile_id) - .await - .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { - id: profile_id.to_string(), - })?; - let mandate_id = request .mandate_id .as_ref() @@ -297,7 +339,7 @@ impl .map(|(payment_method_data, additional_payment_data)| { payment_method_data.apply_additional_payment_data(additional_payment_data) }); - + let amount = payment_attempt.get_total_amount().into(); let payment_data = PaymentData { flow: PhantomData, payment_intent, @@ -333,6 +375,7 @@ impl payment_link_data, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { @@ -382,11 +425,19 @@ impl Domain, _storage_scheme: enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await + helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await } #[instrument(skip_all)] @@ -395,7 +446,7 @@ impl Domain, + _schedule_time: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } @@ -530,15 +581,16 @@ impl ValidateRequest, )> { helpers::validate_customer_details_in_request(request)?; - - if let Some(payment_link_object) = &request.payment_link_object { - helpers::validate_payment_link_request( - payment_link_object, - request.confirm, - request.order_details.clone(), - )?; + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; } + if let Some(payment_link) = &request.payment_link { + if *payment_link { + helpers::validate_payment_link_request(request.confirm)?; + } + }; + let payment_id = request.payment_id.clone().ok_or(error_stack::report!( errors::ApiErrorResponse::PaymentNotFound ))?; @@ -642,6 +694,12 @@ impl PaymentCreate { } else { utils::get_payment_attempt_id(payment_id, 1) }; + let surcharge_amount = request + .surcharge_details + .map(|surcharge_details| surcharge_details.surcharge_amount); + let tax_amount = request + .surcharge_details + .and_then(|surcharge_details| surcharge_details.tax_amount); Ok(( storage::PaymentAttemptNew { @@ -667,6 +725,8 @@ impl PaymentCreate { payment_token: request.payment_token.clone(), mandate_id: request.mandate_id.clone(), business_sub_label: request.business_sub_label.clone(), + surcharge_amount, + tax_amount, mandate_details: request .mandate_data .as_ref() @@ -688,7 +748,8 @@ impl PaymentCreate { payment_link_data: Option, billing_address_id: Option, active_attempt_id: String, - state: &AppState, + profile_id: String, + session_expiry: PrimitiveDateTime, ) -> RouterResult { let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); let status = @@ -702,17 +763,6 @@ impl PaymentCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert order details to value")?; - // If profile id is not passed, get it from the business_country and business_label - let profile_id = core_utils::get_profile_id_from_business_details( - request.business_country, - request.business_label.as_ref(), - merchant_account, - request.profile_id.as_ref(), - &*state.store, - true, - ) - .await?; - let allowed_payment_method_types = request .get_allowed_payment_method_types_as_value() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -775,6 +825,8 @@ impl PaymentCreate { request_incremental_authorization, incremental_authorization_allowed: None, authorization_count: None, + fingerprint_id: None, + session_expiry: Some(session_expiry), }) } @@ -816,32 +868,31 @@ pub fn payments_create_request_validation( #[allow(clippy::too_many_arguments)] async fn create_payment_link( request: &api::PaymentsRequest, - payment_link_object: api_models::payments::PaymentLinkObject, + payment_link_config: api_models::admin::PaymentLinkConfig, merchant_id: String, payment_id: String, db: &dyn StorageInterface, - state: &AppState, amount: api::Amount, description: Option, + profile_id: String, + domain_name: String, + session_expiry: PrimitiveDateTime, ) -> RouterResult> { let created_at @ last_modified_at = Some(common_utils::date_time::now()); - let domain = if let Some(domain_name) = payment_link_object.merchant_custom_domain_name { - format!("https://{domain_name}") - } else { - state.conf.server.base_url.clone() - }; - let payment_link_id = utils::generate_id(consts::ID_LENGTH, "plink"); let payment_link = format!( "{}/payment_link/{}/{}", - domain, + domain_name, merchant_id.clone(), payment_id.clone() ); - let payment_link_config = payment_link_object.payment_link_config.map(|pl_config|{ - common_utils::ext_traits::Encode::::encode_to_value(&pl_config) - }).transpose().change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "payment_link_config" })?; + let payment_link_config_encoded_value = common_utils::ext_traits::Encode::< + api_models::admin::PaymentLinkConfig, + >::encode_to_value(&payment_link_config) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", + })?; let payment_link_req = storage::PaymentLinkNew { payment_link_id: payment_link_id.clone(), @@ -852,10 +903,11 @@ async fn create_payment_link( currency: request.currency, created_at, last_modified_at, - fulfilment_time: payment_link_object.link_expiry, + fulfilment_time: Some(session_expiry), + custom_merchant_name: Some(payment_link_config.seller_name), description, - payment_link_config, - custom_merchant_name: payment_link_object.custom_merchant_name, + payment_link_config: Some(payment_link_config_encoded_value), + profile_id: Some(profile_id), }; let payment_link_db = db .insert_payment_link(payment_link_req) diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 693fce23684..9ea347afd73 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -186,6 +186,7 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + frm_metadata: None, }, Some(payments::CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 03bf6dd46b6..37c7dfd1bae 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -100,7 +100,7 @@ impl .await?; let currency = payment_attempt.currency.get_required_value("currency")?; - let amount = payment_attempt.amount.into(); + let amount = payment_attempt.get_total_amount().into(); let frm_response = db .find_fraud_check_by_payment_id(payment_intent.payment_id.clone(), merchant_account.merchant_id.clone()) @@ -160,6 +160,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index f92487d74a7..9ab0b4f817f 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -24,10 +24,7 @@ use crate::{ services::RedirectForm, types::{ self, api, - storage::{ - self, enums, - payment_attempt::{AttemptStatusExt, PaymentAttemptExt}, - }, + storage::{self, enums}, transformers::{ForeignFrom, ForeignTryFrom}, CaptureSyncResponse, }, @@ -474,20 +471,26 @@ async fn payment_response_update_tracker( flow_name.clone(), ) .await; - let status = + let status = match err.attempt_status { + // Use the status sent by connector in error_response if it's present + Some(status) => status, + None => // mark previous attempt status for technical failures in PSync flow - if flow_name == "PSync" { - match err.status_code { - // marking failure for 2xx because this is genuine payment failure - 200..=299 => storage::enums::AttemptStatus::Failure, - _ => router_data.status, - } - } else { - match err.status_code { - 500..=511 => storage::enums::AttemptStatus::Pending, - _ => storage::enums::AttemptStatus::Failure, + { + if flow_name == "PSync" { + match err.status_code { + // marking failure for 2xx because this is genuine payment failure + 200..=299 => storage::enums::AttemptStatus::Failure, + _ => router_data.status, + } + } else { + match err.status_code { + 500..=511 => storage::enums::AttemptStatus::Pending, + _ => storage::enums::AttemptStatus::Failure, + } } - }; + } + }; ( None, Some(storage::PaymentAttemptUpdate::ErrorUpdate { @@ -496,15 +499,9 @@ async fn payment_response_update_tracker( error_message: Some(Some(err.message)), error_code: Some(Some(err.code)), error_reason: Some(err.reason), - amount_capturable: if status.is_terminal_status() - || router_data - .status - .maps_to_intent_status(enums::IntentStatus::Processing) - { - Some(0) - } else { - None - }, + amount_capturable: router_data + .request + .get_amount_capturable(&payment_data, status), 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), @@ -595,27 +592,33 @@ async fn payment_response_update_tracker( payment_data.payment_attempt.merchant_id.clone(), ); - let (capture_updates, payment_attempt_update) = - match payment_data.multiple_capture_data { - Some(multiple_capture_data) => { - let capture_update = storage::CaptureUpdate::ResponseUpdate { - status: enums::CaptureStatus::foreign_try_from(router_data.status)?, - connector_capture_id: connector_transaction_id.clone(), - connector_response_reference_id, - }; - let capture_update_list = vec![( - multiple_capture_data.get_latest_capture().clone(), - capture_update, - )]; - (Some((multiple_capture_data, capture_update_list)), None) - } - None => ( + let (capture_updates, payment_attempt_update) = match payment_data + .multiple_capture_data + { + Some(multiple_capture_data) => { + let capture_update = storage::CaptureUpdate::ResponseUpdate { + status: enums::CaptureStatus::foreign_try_from(router_data.status)?, + connector_capture_id: connector_transaction_id.clone(), + connector_response_reference_id, + }; + let capture_update_list = vec![( + multiple_capture_data.get_latest_capture().clone(), + capture_update, + )]; + (Some((multiple_capture_data, capture_update_list)), None) + } + None => { + let status = router_data.get_attempt_status_for_db_update(&payment_data); + ( None, Some(storage::PaymentAttemptUpdate::ResponseUpdate { - status: router_data.get_attempt_status_for_db_update(&payment_data), + status, connector: None, connector_transaction_id: connector_transaction_id.clone(), authentication_type: None, + amount_capturable: router_data + .request + .get_amount_capturable(&payment_data, status), payment_method_id: Some(router_data.payment_method_id), mandate_id: payment_data .mandate_id @@ -629,21 +632,13 @@ async fn payment_response_update_tracker( unified_code: error_status.clone(), unified_message: error_status, connector_response_reference_id, - amount_capturable: if router_data.status.is_terminal_status() - || router_data - .status - .maps_to_intent_status(enums::IntentStatus::Processing) - { - Some(0) - } else { - None - }, updated_by: storage_scheme.to_string(), authentication_data, encoded_data, }), - ), - }; + ) + } + }; (capture_updates, payment_attempt_update) } @@ -897,7 +892,7 @@ fn get_total_amount_captured( } None => { //Non multiple capture - let amount = request.get_capture_amount(payment_data); + let amount = request.get_captured_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_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 572bc710b96..9a58dd5af76 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -66,18 +66,7 @@ impl "create a session token for", )?; - let intent_fulfillment_time = helpers::get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - - helpers::authenticate_client_secret( - Some(&request.client_secret), - &payment_intent, - intent_fulfillment_time, - )?; + helpers::authenticate_client_secret(Some(&request.client_secret), &payment_intent)?; let mut payment_attempt = db .find_payment_attempt_by_payment_id_merchant_id_attempt_id( @@ -93,7 +82,7 @@ impl payment_attempt.payment_method = Some(storage_enums::PaymentMethod::Wallet); - let amount = payment_intent.amount.into(); + let amount = payment_attempt.get_total_amount().into(); let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, @@ -197,6 +186,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -322,6 +312,7 @@ where _payment_data: &mut PaymentData, _storage_scheme: storage_enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, ) -> RouterResult<( BoxedOperation<'b, F, api::PaymentsSessionRequest, Ctx>, Option, diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 887edd030d1..557c5c0bd8c 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -66,17 +66,9 @@ impl "update", )?; - let intent_fulfillment_time = helpers::get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - helpers::authenticate_client_secret( payment_intent.client_secret.as_ref(), &payment_intent, - intent_fulfillment_time, )?; payment_attempt = db .find_payment_attempt_by_payment_id_merchant_id_attempt_id( @@ -89,7 +81,7 @@ impl .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); let shipping_address = helpers::create_or_find_address_for_payment_by_request( db, @@ -171,6 +163,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -285,6 +278,7 @@ where payment_data: &mut PaymentData, _storage_scheme: storage_enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsStartRequest, Ctx>, Option, @@ -296,7 +290,14 @@ where .map(|connector_name| connector_name == *"bluesnap".to_string()) .unwrap_or(false) { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await + helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await } else { Ok((Box::new(self), None)) } diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 0320cf50663..c6d9a30f0c9 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -96,11 +96,19 @@ impl Domain, _storage_scheme: enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await + helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await } #[instrument(skip_all)] @@ -229,22 +237,12 @@ async fn get_tracker_for_sync< ) .await?; - let intent_fulfillment_time = helpers::get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - helpers::authenticate_client_secret( - request.client_secret.as_ref(), - &payment_intent, - intent_fulfillment_time, - )?; + helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?; let payment_id_str = payment_attempt.payment_id.clone(); currency = payment_attempt.currency.get_required_value("currency")?; - amount = payment_attempt.amount.into(); + amount = payment_attempt.get_total_amount().into(); let shipping_address = helpers::get_address_by_id( db, @@ -423,6 +421,7 @@ async fn get_tracker_for_sync< frm_message: frm_response.ok(), incremental_authorization_details: None, authorizations, + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index f1a35cffce8..e002b92d181 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use api_models::enums::FrmSuggestion; +use api_models::{enums::FrmSuggestion, payments::RequestSurchargeDetails}; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; use error_stack::{report, IntoReport, ResultExt}; @@ -64,6 +64,7 @@ impl helpers::validate_order_details_amount( order_details.to_owned(), payment_intent.amount, + false, )?; } @@ -86,18 +87,7 @@ impl "update", )?; - let intent_fulfillment_time = helpers::get_merchant_fullfillment_time( - payment_intent.payment_link_id.clone(), - merchant_account.intent_fulfillment_time, - db, - ) - .await?; - - helpers::authenticate_client_secret( - request.client_secret.as_ref(), - &payment_intent, - intent_fulfillment_time, - )?; + helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?; let ( token, payment_method, @@ -281,11 +271,25 @@ impl }) .await .transpose()?; - let next_operation: BoxedOperation<'a, F, api::PaymentsRequest, Ctx> = + let (next_operation, amount): (BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, _) = if request.confirm.unwrap_or(false) { - Box::new(operations::PaymentConfirm) + let amount = { + let amount = request + .amount + .map(Into::into) + .unwrap_or(payment_attempt.amount); + payment_attempt.amount = amount; + payment_intent.amount = amount; + let surcharge_amount = request + .surcharge_details + .as_ref() + .map(RequestSurchargeDetails::get_total_surcharge_amount) + .or(payment_attempt.get_total_surcharge_amount()); + (amount + surcharge_amount.unwrap_or(0)).into() + }; + (Box::new(operations::PaymentConfirm), amount) } else { - Box::new(self) + (Box::new(self), amount) }; payment_intent.status = match request.payment_method_data.as_ref() { @@ -375,6 +379,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { @@ -424,11 +429,19 @@ impl Domain, _storage_scheme: storage_enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, + customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, api::PaymentsRequest, Ctx>, Option, )> { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await + helpers::make_pm_data( + Box::new(self), + state, + payment_data, + merchant_key_store, + customer, + ) + .await } #[instrument(skip_all)] @@ -580,11 +593,12 @@ impl .clone(); let order_details = payment_data.payment_intent.order_details.clone(); let metadata = payment_data.payment_intent.metadata.clone(); + let session_expiry = payment_data.payment_intent.session_expiry; payment_data.payment_intent = state .store .update_payment_intent( - payment_data.payment_intent, + payment_data.payment_intent.clone(), storage::PaymentIntentUpdate::Update { amount: payment_data.amount.into(), currency: payment_data.currency, @@ -603,6 +617,8 @@ impl metadata, payment_confirm_source: None, updated_by: storage_scheme.to_string(), + fingerprint_id: None, + session_expiry, }, storage_scheme, ) @@ -631,6 +647,9 @@ impl ValidateRequest, )> { helpers::validate_customer_details_in_request(request)?; + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; + } let payment_id = request .payment_id .clone() diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 7346c46df12..51fdff77c3b 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -92,7 +92,7 @@ impl .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; let currency = payment_attempt.currency.get_required_value("currency")?; - let amount = payment_attempt.amount; + let amount = payment_attempt.get_total_amount(); let profile_id = payment_intent .profile_id @@ -149,6 +149,7 @@ impl authorization_id: None, }), authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -308,6 +309,7 @@ impl _payment_data: &mut payments::PaymentData, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, ) -> RouterResult<( BoxedOperation<'a, F, PaymentsIncrementalAuthorizationRequest, Ctx>, Option, diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 551d1c8abb9..f884cb79e7e 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -198,6 +198,7 @@ pub async fn save_in_locker( payment_method_id: pm_id, payment_method: payment_method_request.payment_method, payment_method_type: payment_method_request.payment_method_type, + bank_transfer: None, card: None, metadata: None, created: Some(common_utils::date_time::now()), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index bd6d03e5625..5ab6bffc8e6 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -118,7 +118,7 @@ where let apple_pay_flow = payments::decide_apple_pay_flow( &payment_data.payment_attempt.payment_method_type, - &Some(merchant_connector_account.clone()), + Some(merchant_connector_account), ); router_data = types::RouterData { @@ -165,6 +165,7 @@ where connector_http_status_code: None, external_latency: None, apple_pay_flow, + frm_metadata: None, }; Ok(router_data) @@ -546,7 +547,7 @@ where if third_party_sdk_session_next_action(&payment_attempt, operation) { next_action_response = Some( api_models::payments::NextActionData::ThirdPartySdkSessionToken { - session_token: payment_data.sessions_token.get(0).cloned(), + session_token: payment_data.sessions_token.first().cloned(), }, ) } @@ -564,6 +565,7 @@ where }); services::ApplicationResponse::JsonWithHeaders(( response + .set_net_amount(payment_attempt.net_amount) .set_payment_id(Some(payment_attempt.payment_id)) .set_merchant_id(Some(payment_attempt.merchant_id)) .set_status(payment_intent.status) @@ -704,8 +706,10 @@ where .set_incremental_authorization_allowed( payment_intent.incremental_authorization_allowed, ) + .set_fingerprint(payment_intent.fingerprint_id) .set_authorization_count(payment_intent.authorization_count) .set_incremental_authorizations(incremental_authorizations_response) + .set_expires_on(payment_intent.session_expiry) .to_owned(), headers, )) @@ -713,6 +717,7 @@ where } None => services::ApplicationResponse::JsonWithHeaders(( api::PaymentsResponse { + net_amount: payment_attempt.net_amount, payment_id: Some(payment_attempt.payment_id), merchant_id: Some(payment_attempt.merchant_id), status: payment_intent.status, @@ -771,6 +776,7 @@ where incremental_authorization_allowed: payment_intent.incremental_authorization_allowed, authorization_count: payment_intent.authorization_count, incremental_authorizations: incremental_authorizations_response, + expires_on: payment_intent.session_expiry, ..Default::default() }, headers, @@ -945,6 +951,11 @@ pub fn change_order_details_to_new_type( quantity: order_details.quantity, amount: order_amount, product_img_link: order_details.product_img_link, + requires_shipping: order_details.requires_shipping, + product_id: order_details.product_id, + category: order_details.category, + brand: order_details.brand, + product_type: order_details.product_type, }]) } @@ -1032,6 +1043,11 @@ impl TryFrom> for types::PaymentsAuthoriz None } }); + let amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.final_amount) + .unwrap_or(payment_data.amount.into()); Ok(Self { payment_method_data: payment_method_data.get_required_value("payment_method_data")?, setup_future_usage: payment_data.payment_intent.setup_future_usage, @@ -1042,7 +1058,7 @@ impl TryFrom> for types::PaymentsAuthoriz statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, statement_descriptor: payment_data.payment_intent.statement_descriptor_name, capture_method: payment_data.payment_attempt.capture_method, - amount: payment_data.amount.into(), + amount, currency: payment_data.currency, browser_info, email: payment_data.email, @@ -1062,8 +1078,10 @@ impl TryFrom> for types::PaymentsAuthoriz payment_data .payment_intent .request_incremental_authorization, - RequestIncrementalAuthorization::True | RequestIncrementalAuthorization::Default + Some(RequestIncrementalAuthorization::True) + | Some(RequestIncrementalAuthorization::Default) ), + metadata: additional_data.payment_data.payment_intent.metadata, }) } } @@ -1208,6 +1226,7 @@ impl TryFrom> for types::PaymentsCaptureD None => None, }, browser_info, + metadata: payment_data.payment_intent.metadata, }) } } @@ -1242,6 +1261,7 @@ impl TryFrom> for types::PaymentsCancelDa cancellation_reason: payment_data.payment_attempt.cancellation_reason, connector_meta: payment_data.payment_attempt.connector_metadata, browser_info, + metadata: payment_data.payment_intent.metadata, }) } } @@ -1294,9 +1314,14 @@ impl TryFrom> for types::PaymentsSessionD .collect::, _>>() }) .transpose()?; + let amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.final_amount) + .unwrap_or(payment_data.amount.into()); Ok(Self { - amount: payment_data.amount.into(), + amount, currency: payment_data.currency, country: payment_data.address.billing.and_then(|billing_address| { billing_address.address.and_then(|address| address.country) @@ -1350,7 +1375,8 @@ impl TryFrom> for types::SetupMandateRequ payment_data .payment_intent .request_incremental_authorization, - RequestIncrementalAuthorization::True | RequestIncrementalAuthorization::Default + Some(RequestIncrementalAuthorization::True) + | Some(RequestIncrementalAuthorization::Default) ), }) } @@ -1401,6 +1427,9 @@ impl TryFrom> for types::CompleteAuthoriz fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; + let router_base_url = &additional_data.router_base_url; + let connector_name = &additional_data.connector_name; + let attempt = &payment_data.payment_attempt; let browser_info: Option = payment_data .payment_attempt .browser_info @@ -1417,7 +1446,16 @@ impl TryFrom> for types::CompleteAuthoriz payload: redirect.json_payload, } }); - + let amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.final_amount) + .unwrap_or(payment_data.amount.into()); + let complete_authorize_url = Some(helpers::create_complete_authorize_url( + router_base_url, + attempt, + connector_name, + )); Ok(Self { setup_future_usage: payment_data.payment_intent.setup_future_usage, mandate_id: payment_data.mandate_id.clone(), @@ -1426,7 +1464,7 @@ impl TryFrom> for types::CompleteAuthoriz confirm: payment_data.payment_attempt.confirm, statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, capture_method: payment_data.payment_attempt.capture_method, - amount: payment_data.amount.into(), + amount, currency: payment_data.currency, browser_info, email: payment_data.email, @@ -1434,6 +1472,8 @@ impl TryFrom> for types::CompleteAuthoriz connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, redirect_response, connector_meta: payment_data.payment_attempt.connector_metadata, + complete_authorize_url, + metadata: payment_data.payment_intent.metadata, }) } } @@ -1491,12 +1531,17 @@ impl TryFrom> for types::PaymentsPreProce .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "browser_info", })?; + let amount = payment_data + .surcharge_details + .as_ref() + .map(|surcharge_details| surcharge_details.final_amount) + .unwrap_or(payment_data.amount.into()); Ok(Self { payment_method_data, email: payment_data.email, currency: Some(payment_data.currency), - amount: Some(payment_data.amount.into()), + amount: Some(amount), payment_method_type: payment_data.payment_attempt.payment_method_type, setup_mandate_details: payment_data.setup_mandate, capture_method: payment_data.payment_attempt.capture_method, @@ -1507,6 +1552,7 @@ impl TryFrom> for types::PaymentsPreProce browser_info, surcharge_details: payment_data.surcharge_details, connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, + redirect_response: None, }) } } diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 001082d2c92..41a8850ab3d 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, num::TryFromIntError}; use api_models::{payment_methods::SurchargeDetailsResponse, payments::RequestSurchargeDetails}; use common_utils::{consts, errors::CustomResult, ext_traits::Encode, types as common_types}; use data_models::payments::payment_attempt::PaymentAttempt; +use diesel_models::business_profile::BusinessProfile; use error_stack::{IntoReport, ResultExt}; use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; @@ -12,7 +13,6 @@ use crate::{ core::errors::{self, RouterResult}, routes::AppState, types::{ - domain, storage::{self, enums as storage_enums}, transformers::ForeignTryFrom, }, @@ -178,6 +178,8 @@ impl MultipleCaptureData { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct SurchargeDetails { + /// original_amount + pub original_amount: i64, /// surcharge value pub surcharge: common_types::Surcharge, /// tax on surcharge value @@ -198,6 +200,7 @@ impl From<(&RequestSurchargeDetails, &PaymentAttempt)> for SurchargeDetails { let surcharge_amount = request_surcharge_details.surcharge_amount; let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or(0); Self { + original_amount: payment_attempt.amount, surcharge: common_types::Surcharge::Fixed(request_surcharge_details.surcharge_amount), tax_on_surcharge: None, surcharge_amount, @@ -219,13 +222,15 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe currency.to_currency_base_unit_asf64(surcharge_details.tax_on_surcharge_amount)?; let display_final_amount = currency.to_currency_base_unit_asf64(surcharge_details.final_amount)?; + let display_total_surcharge_amount = currency.to_currency_base_unit_asf64( + surcharge_details.surcharge_amount + surcharge_details.tax_on_surcharge_amount, + )?; Ok(Self { surcharge: surcharge_details.surcharge.clone().into(), tax_on_surcharge: surcharge_details.tax_on_surcharge.clone().map(Into::into), display_surcharge_amount, display_tax_on_surcharge_amount, - display_total_surcharge_amount: display_surcharge_amount - + display_tax_on_surcharge_amount, + display_total_surcharge_amount, display_final_amount, }) } @@ -317,7 +322,7 @@ impl SurchargeMetadata { pub async fn persist_individual_surcharge_details_in_redis( &self, state: &AppState, - merchant_account: &domain::MerchantAccount, + business_profile: &BusinessProfile, ) -> RouterResult<()> { if !self.is_empty_result() { let redis_conn = state @@ -336,7 +341,7 @@ impl SurchargeMetadata { .attach_printable("Failed to encode to string of json")?, )); } - let intent_fulfillment_time = merchant_account + let intent_fulfillment_time = business_profile .intent_fulfillment_time .unwrap_or(router_consts::DEFAULT_FULFILLMENT_TIME); redis_conn diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 9ddc8395738..56e3a6faf53 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -1,4 +1,7 @@ -use common_utils::{errors::CustomResult, ext_traits::ValueExt}; +use common_utils::{ + errors::CustomResult, + ext_traits::{StringExt, ValueExt}, +}; use diesel_models::encryption::Encryption; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -40,28 +43,50 @@ pub async fn make_payout_method_data<'a>( merchant_key_store: &domain::MerchantKeyStore, ) -> RouterResult> { let db = &*state.store; + let certain_payout_type = payout_type.get_required_value("payout_type")?.to_owned(); let hyperswitch_token = if let Some(payout_token) = payout_token { - let key = format!( - "pm_token_{}_{}_hyperswitch", - payout_token, - api_enums::PaymentMethod::foreign_from( - payout_type.get_required_value("payout_type")?.to_owned() - ) - ); - - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; - - let hyperswitch_token_option = redis_conn - .get_key::>(&key) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch the token from redis")?; + if payout_token.starts_with("temporary_token_") { + Some(payout_token.to_string()) + } else { + let key = format!( + "pm_token_{}_{}_hyperswitch", + payout_token, + api_enums::PaymentMethod::foreign_from(certain_payout_type) + ); + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; - hyperswitch_token_option.or(Some(payout_token.to_string())) + let hyperswitch_token = redis_conn + .get_key::>(&key) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch the token from redis")? + .ok_or(error_stack::Report::new( + errors::ApiErrorResponse::UnprocessableEntity { + message: "Token is invalid or expired".to_owned(), + }, + ))?; + let payment_token_data = hyperswitch_token + .clone() + .parse_struct("PaymentTokenData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to deserialize hyperswitch token data")?; + + let payment_token = match payment_token_data { + storage::PaymentTokenData::PermanentCard(storage::CardTokenData { token }) => { + Some(token) + } + storage::PaymentTokenData::TemporaryGeneric(storage::GenericTokenData { + token, + }) => Some(token), + _ => None, + }; + payment_token.or(Some(payout_token.to_string())) + } } else { None }; @@ -69,8 +94,10 @@ pub async fn make_payout_method_data<'a>( match (payout_method_data.to_owned(), hyperswitch_token) { // Get operation (None, Some(payout_token)) => { - let (pm, supplementary_data) = - vault::Vault::get_payout_method_data_from_temporary_locker( + if payout_token.starts_with("temporary_token_") + || certain_payout_type == api_enums::PayoutType::Bank + { + let (pm, supplementary_data) = vault::Vault::get_payout_method_data_from_temporary_locker( state, &payout_token, merchant_key_store, @@ -79,15 +106,33 @@ pub async fn make_payout_method_data<'a>( .attach_printable( "Payout method for given token not found or there was a problem fetching it", )?; - utils::when( - supplementary_data - .customer_id - .ne(&Some(customer_id.to_owned())), - || { - Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payout method and customer passed in payout are not same".into() }) - }, - )?; - Ok(pm) + utils::when( + supplementary_data + .customer_id + .ne(&Some(customer_id.to_owned())), + || { + Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payout method and customer passed in payout are not same".into() }) + }, + )?; + Ok(pm) + } else { + let resp = cards::get_card_from_locker( + state, + customer_id, + merchant_id, + payout_token.as_ref(), + ) + .await + .attach_printable("Payout method [card] could not be fetched from HS locker")?; + Ok(Some({ + api::PayoutMethodData::Card(api::CardPayout { + card_number: resp.card_number, + expiry_month: resp.card_exp_month, + expiry_year: resp.card_exp_year, + card_holder_name: resp.name_on_card, + }) + })) + } } // Create / Update operation @@ -131,11 +176,11 @@ pub async fn save_payout_data_to_locker( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { - let (locker_req, card_details, payment_method_type) = match payout_method_data { + let (locker_req, card_details, bank_details, payment_method_type) = match payout_method_data { api_models::payouts::PayoutMethodData::Card(card) => { let card_detail = api::CardDetail { card_number: card.card_number.to_owned(), - card_holder_name: Some(card.card_holder_name.to_owned()), + card_holder_name: card.card_holder_name.to_owned(), card_exp_month: card.expiry_month.to_owned(), card_exp_year: card.expiry_year.to_owned(), nick_name: None, @@ -145,7 +190,7 @@ pub async fn save_payout_data_to_locker( merchant_customer_id: payout_attempt.customer_id.to_owned(), card: transformers::Card { card_number: card.card_number.to_owned(), - name_on_card: Some(card.card_holder_name.to_owned()), + name_on_card: card.card_holder_name.to_owned(), card_exp_month: card.expiry_month.to_owned(), card_exp_year: card.expiry_year.to_owned(), card_brand: None, @@ -157,6 +202,7 @@ pub async fn save_payout_data_to_locker( ( payload, Some(card_detail), + None, api_enums::PaymentMethodType::Debit, ) } @@ -191,6 +237,7 @@ pub async fn save_payout_data_to_locker( ( payload, None, + Some(bank.to_owned()), api_enums::PaymentMethodType::foreign_from(bank.to_owned()), ) } @@ -244,6 +291,7 @@ pub async fn save_payout_data_to_locker( payment_method_type: Some(payment_method_type), payment_method_issuer: None, payment_method_issuer_code: None, + bank_transfer: bank_details, card: card_details, metadata: None, customer_id: Some(payout_attempt.customer_id.to_owned()), diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 821f049d8cf..0750ff82bf5 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -52,6 +52,7 @@ use crate::{ storage, transformers::ForeignTryFrom, }, + utils::ext_traits::OptionExt, }; pub async fn create_link_token( @@ -618,6 +619,7 @@ pub async fn retrieve_payment_method_from_auth_service( key_store: &domain::MerchantKeyStore, auth_token: &payment_methods::BankAccountConnectorDetails, payment_intent: &PaymentIntent, + customer: &Option, ) -> RouterResult> { let db = state.store.as_ref(); @@ -710,10 +712,17 @@ pub async fn retrieve_payment_method_from_auth_service( last_name, } }); + + let email = customer + .as_ref() + .and_then(|customer| customer.email.clone()) + .map(common_utils::pii::Email::from) + .get_required_value("email")?; + let payment_method_data = PaymentMethodData::BankDebit(BankDebitData::AchBankDebit { billing_details: BankDebitBilling { name: name.unwrap_or_default(), - email: common_utils::pii::Email::from(masking::Secret::new("".to_string())), + email, address: address_details, }, account_number: masking::Secret::new(bank_account.account_number.clone()), diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index c43c00b7259..e60c341dedc 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -610,7 +610,11 @@ pub async fn validate_and_create_refund( ), })?; - validator::validate_refund_amount(payment_attempt.amount, &all_refunds, refund_amount) + let total_amount_captured = payment_intent + .amount_captured + .unwrap_or(payment_attempt.amount); + + validator::validate_refund_amount(total_amount_captured, &all_refunds, refund_amount) .change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?; validator::validate_maximum_refund_against_payment_attempt( @@ -646,6 +650,7 @@ pub async fn validate_and_create_refund( .set_attempt_id(payment_attempt.attempt_id.clone()) .set_refund_reason(req.reason) .set_profile_id(payment_intent.profile_id.clone()) + .set_merchant_connector_id(payment_attempt.merchant_connector_id.clone()) .to_owned(); let refund = match db @@ -772,6 +777,7 @@ impl ForeignFrom for api::RefundResponse { created_at: Some(refund.created_at), updated_at: Some(refund.updated_at), connector: refund.connector, + merchant_connector_id: refund.merchant_connector_id, } } } @@ -929,7 +935,9 @@ pub async fn start_refund_workflow( refund_tracker: &storage::ProcessTracker, ) -> Result<(), errors::ProcessTrackerError> { match refund_tracker.name.as_deref() { - Some("EXECUTE_REFUND") => trigger_refund_execute_workflow(state, refund_tracker).await, + Some("EXECUTE_REFUND") => { + Box::pin(trigger_refund_execute_workflow(state, refund_tracker)).await + } Some("SYNC_REFUND") => { Box::pin(sync_refund_with_gateway_workflow(state, refund_tracker)).await } diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 6198a6f79a6..cae8f0494be 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -17,7 +17,7 @@ pub const DEFAULT_LIMIT: i64 = 10; pub enum RefundValidationError { #[error("The payment attempt was not successful")] UnsuccessfulPaymentAttempt, - #[error("The refund amount exceeds the payment amount")] + #[error("The refund amount exceeds the amount captured")] RefundAmountExceedsPaymentAmount, #[error("The order has expired")] OrderExpired, @@ -40,7 +40,7 @@ pub fn validate_success_transaction( #[instrument(skip_all)] pub fn validate_refund_amount( - payment_attempt_amount: i64, // &storage::PaymentAttempt, + amount_captured: i64, all_refunds: &[storage::Refund], refund_amount: i64, ) -> CustomResult<(), RefundValidationError> { @@ -58,7 +58,7 @@ pub fn validate_refund_amount( .sum(); utils::when( - refund_amount > (payment_attempt_amount - total_refunded_amount), + refund_amount > (amount_captured - total_refunded_amount), || { Err(report!( RefundValidationError::RefundAmountExceedsPaymentAmount diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 6eec39f53bc..c86e527b00d 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -255,6 +255,8 @@ pub async fn update_business_profile_active_algorithm_ref( applepay_verified_domains: None, modified_at: None, is_recon_enabled: None, + payment_link_config: None, + session_expiry: None, }; db.update_business_profile_by_profile_id(current_business_profile, business_profile_update) .await diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index e2c538ca80e..27a4f67618e 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1,5 +1,5 @@ use api_models::user as user_api; -use diesel_models::{enums::UserStatus, user as storage_user}; +use diesel_models::{enums::UserStatus, user as storage_user, user_role::UserRoleNew}; #[cfg(feature = "email")] use error_stack::IntoReport; use error_stack::ResultExt; @@ -14,7 +14,6 @@ use super::errors::{UserErrors, UserResponse}; use crate::services::email::types as email_types; use crate::{ consts, - db::user::UserInterface, routes::AppState, services::{authentication as auth, ApplicationResponse}, types::domain, @@ -228,11 +227,12 @@ pub async fn change_password( request: user_api::ChangePasswordRequest, user_from_token: auth::UserFromToken, ) -> UserResponse<()> { - let user: domain::UserFromStorage = - UserInterface::find_user_by_id(&*state.store, &user_from_token.user_id) - .await - .change_context(UserErrors::InternalServerError)? - .into(); + let user: domain::UserFromStorage = state + .store + .find_user_by_id(&user_from_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); user.compare_password(request.old_password.to_owned()) .change_context(UserErrors::InvalidOldPassword)?; @@ -245,17 +245,18 @@ pub async fn change_password( let new_password_hash = utils::user::password::generate_password_hash(new_password.get_secret())?; - 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)?; + let _ = state + .store + .update_user_by_user_id( + 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) } @@ -339,7 +340,6 @@ pub async fn reset_password( Ok(ApplicationResponse::StatusOk) } -#[cfg(feature = "email")] pub async fn invite_user( state: AppState, request: user_api::InviteUserRequest, @@ -368,7 +368,6 @@ pub async fn invite_user( let invitee_user_from_db = domain::UserFromStorage::from(invitee_user); let now = common_utils::date_time::now(); - use diesel_models::user_role::UserRoleNew; state .store .insert_user_role(UserRoleNew { @@ -380,7 +379,7 @@ pub async fn invite_user( created_by: user_from_token.user_id.clone(), last_modified_by: user_from_token.user_id, created_at: now, - last_modified_at: now, + last_modified: now, }) .await .map_err(|e| { @@ -393,6 +392,7 @@ pub async fn invite_user( Ok(ApplicationResponse::Json(user_api::InviteUserResponse { is_email_sent: false, + password: None, })) } else if invitee_user .as_ref() @@ -400,37 +400,67 @@ pub async fn invite_user( .err() .unwrap_or(false) { - let new_user = domain::NewUser::try_from((request.clone(), user_from_token))?; + let new_user = domain::NewUser::try_from((request.clone(), user_from_token.clone()))?; new_user .insert_user_in_db(state.store.as_ref()) .await .change_context(UserErrors::InternalServerError)?; - new_user - .clone() - .insert_user_role_in_db(state.clone(), request.role_id, UserStatus::InvitationSent) - .await - .change_context(UserErrors::InternalServerError)?; - - let email_contents = email_types::InviteUser { - recipient_email: invitee_email, - user_name: domain::UserName::new(new_user.get_name())?, - settings: state.conf.clone(), - subject: "You have been invited to join Hyperswitch Community!", - }; - let send_email_result = state - .email_client - .compose_and_send_email( - Box::new(email_contents), - state.conf.proxy.https_url.as_ref(), - ) - .await; + let now = common_utils::date_time::now(); + state + .store + .insert_user_role(UserRoleNew { + user_id: new_user.get_user_id().to_owned(), + merchant_id: user_from_token.merchant_id, + role_id: request.role_id, + org_id: user_from_token.org_id, + status: UserStatus::InvitationSent, + created_by: user_from_token.user_id.clone(), + last_modified_by: user_from_token.user_id, + created_at: now, + last_modified: now, + }) + .await + .map_err(|e| { + if e.current_context().is_db_unique_violation() { + e.change_context(UserErrors::UserExists) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; - logger::info!(?send_email_result); + let is_email_sent; + #[cfg(feature = "email")] + { + let email_contents = email_types::InviteUser { + recipient_email: invitee_email, + user_name: domain::UserName::new(new_user.get_name())?, + settings: state.conf.clone(), + subject: "You have been invited to join Hyperswitch Community!", + }; + let send_email_result = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await; + logger::info!(?send_email_result); + is_email_sent = send_email_result.is_ok(); + } + #[cfg(not(feature = "email"))] + { + is_email_sent = false; + } Ok(ApplicationResponse::Json(user_api::InviteUserResponse { - is_email_sent: send_email_result.is_ok(), + is_email_sent, + password: if cfg!(not(feature = "email")) { + Some(new_user.get_password().get_secret()) + } else { + None + }, })) } else { Err(UserErrors::InternalServerError.into()) @@ -505,69 +535,72 @@ pub async fn switch_merchant_id( request: user_api::SwitchMerchantIdRequest, user_from_token: auth::UserFromToken, ) -> UserResponse { - if !utils::user_role::is_internal_role(&user_from_token.role_id) { - let merchant_list = - utils::user_role::get_merchant_ids_for_user(state.clone(), &user_from_token.user_id) - .await?; - if !merchant_list.contains(&request.merchant_id) { - return Err(UserErrors::InvalidRoleOperation.into()) - .attach_printable("User doesn't have access to switch"); - } - } - if user_from_token.merchant_id == request.merchant_id { return Err(UserErrors::InvalidRoleOperation.into()) .attach_printable("User switch to same merchant id."); } - let user = state + let user_roles = state .store - .find_user_by_id(&user_from_token.user_id) + .list_user_roles_by_user_id(&user_from_token.user_id) .await .change_context(UserErrors::InternalServerError)?; - let key_store = state - .store - .get_merchant_key_store_by_merchant_id( - request.merchant_id.as_str(), - &state.store.get_master_key().to_vec().into(), - ) - .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(UserErrors::MerchantIdNotFound) - } else { - e.change_context(UserErrors::InternalServerError) - } - })?; + let active_user_roles = user_roles + .into_iter() + .filter(|role| role.status == UserStatus::Active) + .collect::>(); - let _org_id = state - .store - .find_merchant_account_by_merchant_id(request.merchant_id.as_str(), &key_store) - .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(UserErrors::MerchantIdNotFound) - } else { - e.change_context(UserErrors::InternalServerError) - } - })? - .organization_id; + let user = user_from_token.get_user(state.clone()).await?.into(); - let user = domain::UserFromStorage::from(user); - let user_role = state - .store - .find_user_role_by_user_id(user.get_user_id()) - .await - .change_context(UserErrors::InternalServerError)?; + let (token, role_id) = if utils::user_role::is_internal_role(&user_from_token.role_id) { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + request.merchant_id.as_str(), + &state.store.get_master_key().to_vec().into(), + ) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::MerchantIdNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + + let org_id = state + .store + .find_merchant_account_by_merchant_id(request.merchant_id.as_str(), &key_store) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::MerchantIdNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })? + .organization_id; - let token = utils::user::generate_jwt_auth_token_with_custom_merchant_id( - state, - &user, - &user_role, - request.merchant_id.clone(), - ) - .await?; + let token = utils::user::generate_jwt_auth_token_with_custom_role_attributes( + state, + &user, + request.merchant_id.clone(), + org_id, + user_from_token.role_id.clone(), + ) + .await?; + (token, user_from_token.role_id) + } else { + let user_role = active_user_roles + .iter() + .find(|role| role.merchant_id == request.merchant_id) + .ok_or(UserErrors::InvalidRoleOperation.into()) + .attach_printable("User doesn't have access to switch")?; + + let token = utils::user::generate_jwt_auth_token(state, &user, user_role).await?; + (token, user_role.role_id.clone()) + }; Ok(ApplicationResponse::Json( user_api::SwitchMerchantResponse { @@ -576,8 +609,8 @@ pub async fn switch_merchant_id( email: user.get_email(), user_id: user.get_user_id().to_string(), verification_days_left: None, - user_role: user_role.role_id, - merchant_id: user_role.merchant_id, + user_role: role_id, + merchant_id: request.merchant_id, }, )) } @@ -617,9 +650,23 @@ pub async fn create_merchant_account( pub async fn list_merchant_ids_for_user( state: AppState, user: auth::UserFromToken, -) -> UserResponse> { +) -> UserResponse> { + let merchant_ids = utils::user_role::get_merchant_ids_for_user(&state, &user.user_id).await?; + + let merchant_accounts = state + .store + .list_multiple_merchant_accounts(merchant_ids) + .await + .change_context(UserErrors::InternalServerError)?; + Ok(ApplicationResponse::Json( - utils::user::get_merchant_ids_for_user(state, &user.user_id).await?, + merchant_accounts + .into_iter() + .map(|acc| user_api::UserMerchantAccount { + merchant_id: acc.merchant_id, + merchant_name: acc.merchant_name, + }) + .collect(), )) } @@ -670,3 +717,72 @@ pub async fn verify_email( utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, )) } + +#[cfg(feature = "email")] +pub async fn send_verification_mail( + state: AppState, + req: user_api::SendVerifyEmailRequest, +) -> UserResponse<()> { + let user_email = domain::UserEmail::try_from(req.email)?; + let user = state + .store + .find_user_by_email(user_email.clone().get_secret().expose().as_str()) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::UserNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + + if user.is_verified { + return Err(UserErrors::UserAlreadyVerified.into()); + } + + let email_contents = email_types::VerifyEmail { + recipient_email: domain::UserEmail::from_pii_email(user.email)?, + settings: state.conf.clone(), + subject: "Welcome to the Hyperswitch community!", + }; + + state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(ApplicationResponse::StatusOk) +} + +#[cfg(feature = "recon")] +pub async fn verify_token( + state: AppState, + req: auth::ReconUser, +) -> UserResponse { + let user = state + .store + .find_user_by_id(&req.user_id) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::UserNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + let merchant_id = state + .store + .find_user_role_by_user_id(&req.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .merchant_id; + + Ok(ApplicationResponse::Json(user_api::VerifyTokenResponse { + merchant_id: merchant_id.to_string(), + user_email: user.email, + })) +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 724a698ff70..016d5ec955d 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -175,6 +175,7 @@ pub async fn construct_payout_router_data<'a, F>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) @@ -326,6 +327,7 @@ pub async fn construct_refund_router_data<'a, F>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) @@ -555,6 +557,7 @@ pub async fn construct_accept_dispute_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -642,6 +645,7 @@ pub async fn construct_submit_evidence_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -735,6 +739,7 @@ pub async fn construct_upload_file_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -825,6 +830,7 @@ pub async fn construct_defend_dispute_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -908,6 +914,7 @@ pub async fn construct_retrieve_file_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -1066,8 +1073,8 @@ pub fn get_flow_name() -> RouterResult { pub fn get_request_incremental_authorization_value( request_incremental_authorization: Option, capture_method: Option, -) -> RouterResult { - request_incremental_authorization +) -> RouterResult> { + Some(request_incremental_authorization .map(|request_incremental_authorization| { if request_incremental_authorization { if capture_method == Some(common_enums::CaptureMethod::Automatic) { @@ -1078,14 +1085,16 @@ pub fn get_request_incremental_authorization_value( Ok(RequestIncrementalAuthorization::False) } }) - .unwrap_or(Ok(RequestIncrementalAuthorization::default())) + .unwrap_or(Ok(RequestIncrementalAuthorization::default()))).transpose() } pub fn get_incremental_authorization_allowed_value( incremental_authorization_allowed: Option, - request_incremental_authorization: RequestIncrementalAuthorization, + request_incremental_authorization: Option, ) -> Option { - if request_incremental_authorization == common_enums::RequestIncrementalAuthorization::False { + if request_incremental_authorization + == Some(common_enums::RequestIncrementalAuthorization::False) + { Some(false) } else { incremental_authorization_allowed diff --git a/crates/router/src/core/verification.rs b/crates/router/src/core/verification.rs index e643e0455b8..bac47b34dce 100644 --- a/crates/router/src/core/verification.rs +++ b/crates/router/src/core/verification.rs @@ -1,16 +1,11 @@ pub mod utils; use api_models::verifications::{self, ApplepayMerchantResponse}; -use common_utils::{errors::CustomResult, ext_traits::Encode}; +use common_utils::{errors::CustomResult, request::RequestContent}; use error_stack::ResultExt; #[cfg(feature = "kms")] use external_services::kms; -use crate::{ - core::errors::{self, api_error_response}, - headers, logger, - routes::AppState, - services, types, -}; +use crate::{core::errors::api_error_response, headers, logger, routes::AppState, services}; const APPLEPAY_INTERNAL_MERCHANT_NAME: &str = "Applepay_merchant"; @@ -57,13 +52,6 @@ pub async fn verify_merchant_creds_for_applepay( partner_merchant_name: APPLEPAY_INTERNAL_MERCHANT_NAME.to_string(), }; - let applepay_req = types::RequestBody::log_and_get_request_body( - &request_body, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode ApplePay session request to a string of json")?; - let apple_pay_merch_verification_req = services::RequestBuilder::new() .method(services::Method::Post) .url(applepay_endpoint) @@ -72,7 +60,7 @@ pub async fn verify_merchant_creds_for_applepay( headers::CONTENT_TYPE.to_string(), "application/json".to_string().into(), )]) - .body(Some(applepay_req)) + .set_body(RequestContent::Json(Box::new(request_body))) .add_certificate(Some(cert_data)) .add_certificate_key(Some(key_data)) .build(); diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index be8d118a47c..4354a3ee195 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -8,7 +8,7 @@ use api_models::{ payments::HeaderPayload, webhooks::{self, WebhookResponseTracker}, }; -use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; +use common_utils::{errors::ReportSwitchExt, events::ApiEventsType, request::RequestContent}; use error_stack::{report, IntoReport, ResultExt}; use masking::ExposeInterface; use router_env::{instrument, tracing, tracing_actix_web::RequestId}; @@ -25,18 +25,20 @@ use crate::{ payments, refunds, }, db::StorageInterface, - events::api_logs::ApiEvent, + events::{ + api_logs::ApiEvent, + outgoing_webhook_logs::{OutgoingWebhookEvent, OutgoingWebhookEventMetric}, + }, logger, routes::{app::AppStateInfo, lock_utils, metrics::request::add_attributes, AppState}, services::{self, authentication as auth}, types::{ - self as router_types, api::{self, mandates::MandateResponseExt}, domain, storage::{self, enums}, transformers::{ForeignInto, ForeignTryInto}, }, - utils::{self as helper_utils, generate_id, Encode, OptionExt, ValueExt}, + utils::{self as helper_utils, generate_id, OptionExt, ValueExt}, }; const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; @@ -732,21 +734,47 @@ pub async fn create_event_and_trigger_outgoing_webhook(business_profile, outgoing_webhook, &state).await; + trigger_webhook_to_merchant::(business_profile, outgoing_webhook, state).await; if let Err(e) = result { + error.replace( + serde_json::to_value(e.current_context()) + .into_report() + .attach_printable("Failed to serialize json error response") + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .ok() + .into(), + ); logger::error!(?e); } + let outgoing_webhook_event_type = content.get_outgoing_webhook_event_type(); + let webhook_event = OutgoingWebhookEvent::new( + merchant_account.merchant_id.clone(), + event.event_id.clone(), + event_type, + outgoing_webhook_event_type, + error.is_some(), + error, + ); + match webhook_event.clone().try_into() { + Ok(event) => { + state_clone.event_handler().log_event(event); + } + Err(err) => { + logger::error!(error=?err, event=?webhook_event, "Error Logging Outgoing Webhook Event"); + } + } }); } @@ -756,7 +784,7 @@ pub async fn create_event_and_trigger_outgoing_webhook( business_profile: diesel_models::business_profile::BusinessProfile, webhook: api::OutgoingWebhook, - state: &AppState, + state: AppState, ) -> CustomResult<(), errors::WebhooksFlowError> { let webhook_details_json = business_profile .webhook_details @@ -781,13 +809,6 @@ pub async fn trigger_webhook_to_merchant( let outgoing_webhooks_signature = transformed_outgoing_webhook .get_outgoing_webhooks_signature(business_profile.payment_response_hash_key.clone())?; - let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( - &transformed_outgoing_webhook, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) - .attach_printable("There was an issue when encoding the outgoing webhook body")?; - let mut header = vec![( reqwest::header::CONTENT_TYPE.to_string(), "application/json".into(), @@ -802,12 +823,12 @@ pub async fn trigger_webhook_to_merchant( .url(&webhook_url) .attach_default_headers() .headers(header) - .body(Some(transformed_outgoing_webhook_string)) + .set_body(RequestContent::Json(Box::new(transformed_outgoing_webhook))) .build(); let response = state .api_client - .send_request(state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS), false) + .send_request(&state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS), false) .await; metrics::WEBHOOK_OUTGOING_COUNT.add( @@ -917,7 +938,7 @@ pub async fn webhooks_wrapper { @@ -1134,9 +1155,7 @@ pub async fn webhooks_core + Sync + Send + std::fmt::Debug + Serialize + From + Sync + Send + std::fmt::Debug + 'static { fn get_outgoing_webhooks_signature( &self, diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index 322440e5313..08b49048043 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -113,6 +113,7 @@ pub async fn construct_webhook_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 0cd4cb21881..b9d346b7a71 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -1,6 +1,9 @@ pub mod address; pub mod api_keys; pub mod authorization; +pub mod blocklist; +pub mod blocklist_fingerprint; +pub mod blocklist_lookup; pub mod business_profile; pub mod cache; pub mod capture; @@ -14,6 +17,7 @@ pub mod events; pub mod file; pub mod fraud_check; pub mod gsm; +pub mod health_check; mod kafka_store; pub mod locker_mock_up; pub mod mandate; @@ -67,6 +71,7 @@ pub trait StorageInterface: + dyn_clone::DynClone + address::AddressInterface + api_keys::ApiKeyInterface + + blocklist_lookup::BlocklistLookupInterface + configs::ConfigInterface + capture::CaptureInterface + customers::CustomerInterface @@ -84,6 +89,8 @@ pub trait StorageInterface: + PaymentAttemptInterface + PaymentIntentInterface + payment_method::PaymentMethodInterface + + blocklist::BlocklistInterface + + blocklist_fingerprint::BlocklistFingerprintInterface + scheduler::SchedulerInterface + payout_attempt::PayoutAttemptInterface + payouts::PayoutsInterface @@ -103,6 +110,7 @@ pub trait StorageInterface: + user_role::UserRoleInterface + authorization::AuthorizationInterface + user::sample_data::BatchSampleDataInterface + + health_check::HealthCheckInterface + 'static { fn get_scheduler_db(&self) -> Box; diff --git a/crates/router/src/db/api_keys.rs b/crates/router/src/db/api_keys.rs index 4ba9e47e9a5..94edac96902 100644 --- a/crates/router/src/db/api_keys.rs +++ b/crates/router/src/db/api_keys.rs @@ -535,6 +535,7 @@ mod tests { merchant_id, hashed_api_key.into_inner() ),) + .await .is_none() ) } diff --git a/crates/router/src/db/authorization.rs b/crates/router/src/db/authorization.rs index f24daaf718a..d167d177537 100644 --- a/crates/router/src/db/authorization.rs +++ b/crates/router/src/db/authorization.rs @@ -1,3 +1,4 @@ +use diesel_models::authorization::AuthorizationUpdateInternal; use error_stack::IntoReport; use super::{MockDb, Store}; @@ -77,28 +78,71 @@ impl AuthorizationInterface for Store { impl AuthorizationInterface for MockDb { async fn insert_authorization( &self, - _authorization: storage::AuthorizationNew, + authorization: storage::AuthorizationNew, ) -> CustomResult { - // TODO: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? + let mut authorizations = self.authorizations.lock().await; + if authorizations.iter().any(|authorization_inner| { + authorization_inner.authorization_id == authorization.authorization_id + }) { + Err(errors::StorageError::DuplicateValue { + entity: "authorization_id", + key: None, + })? + } + let authorization = storage::Authorization { + authorization_id: authorization.authorization_id, + merchant_id: authorization.merchant_id, + payment_id: authorization.payment_id, + amount: authorization.amount, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + status: authorization.status, + error_code: authorization.error_code, + error_message: authorization.error_message, + connector_authorization_id: authorization.connector_authorization_id, + previously_authorized_amount: authorization.previously_authorized_amount, + }; + authorizations.push(authorization.clone()); + Ok(authorization) } async fn find_all_authorizations_by_merchant_id_payment_id( &self, - _merchant_id: &str, - _payment_id: &str, + merchant_id: &str, + payment_id: &str, ) -> CustomResult, errors::StorageError> { - // TODO: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? + let authorizations = self.authorizations.lock().await; + let authorizations_found: Vec = authorizations + .iter() + .filter(|a| a.merchant_id == merchant_id && a.payment_id == payment_id) + .cloned() + .collect(); + + Ok(authorizations_found) } async fn update_authorization_by_merchant_id_authorization_id( &self, - _merchant_id: String, - _authorization_id: String, - _authorization: storage::AuthorizationUpdate, + merchant_id: String, + authorization_id: String, + authorization_update: storage::AuthorizationUpdate, ) -> CustomResult { - // TODO: Implement function for `MockDb` - Err(errors::StorageError::MockDbError)? + let mut authorizations = self.authorizations.lock().await; + authorizations + .iter_mut() + .find(|authorization| authorization.authorization_id == authorization_id && authorization.merchant_id == merchant_id) + .map(|authorization| { + let authorization_updated = + AuthorizationUpdateInternal::from(authorization_update) + .create_authorization(authorization.clone()); + *authorization = authorization_updated.clone(); + authorization_updated + }) + .ok_or( + errors::StorageError::ValueNotFound(format!( + "cannot find authorization for authorization_id = {authorization_id} and merchant_id = {merchant_id}" + )) + .into(), + ) } } diff --git a/crates/router/src/db/blocklist.rs b/crates/router/src/db/blocklist.rs new file mode 100644 index 00000000000..93361552de7 --- /dev/null +++ b/crates/router/src/db/blocklist.rs @@ -0,0 +1,211 @@ +use error_stack::IntoReport; +use router_env::{instrument, tracing}; +use storage_impl::MockDb; + +use super::Store; +use crate::{ + connection, + core::errors::{self, CustomResult}, + db::kafka_store::KafkaStore, + types::storage, +}; + +#[async_trait::async_trait] +pub trait BlocklistInterface { + async fn insert_blocklist_entry( + &self, + pm_blocklist_new: storage::BlocklistNew, + ) -> CustomResult; + + async fn find_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult; + + async fn delete_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult; + + async fn list_blocklist_entries_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult, errors::StorageError>; + + async fn list_blocklist_entries_by_merchant_id_data_kind( + &self, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, + limit: i64, + offset: i64, + ) -> CustomResult, errors::StorageError>; +} + +#[async_trait::async_trait] +impl BlocklistInterface for Store { + #[instrument(skip_all)] + async fn insert_blocklist_entry( + &self, + pm_blocklist: storage::BlocklistNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + pm_blocklist + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + } + + async fn find_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::Blocklist::find_by_merchant_id_fingerprint_id(&conn, merchant_id, fingerprint_id) + .await + .map_err(Into::into) + .into_report() + } + + async fn list_blocklist_entries_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_write(self).await?; + storage::Blocklist::list_by_merchant_id(&conn, merchant_id) + .await + .map_err(Into::into) + .into_report() + } + + async fn list_blocklist_entries_by_merchant_id_data_kind( + &self, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, + limit: i64, + offset: i64, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_write(self).await?; + storage::Blocklist::list_by_merchant_id_data_kind( + &conn, + merchant_id, + data_kind, + limit, + offset, + ) + .await + .map_err(Into::into) + .into_report() + } + + async fn delete_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::Blocklist::delete_by_merchant_id_fingerprint_id(&conn, merchant_id, fingerprint_id) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl BlocklistInterface for MockDb { + #[instrument(skip_all)] + async fn insert_blocklist_entry( + &self, + _pm_blocklist: storage::BlocklistNew, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn find_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + _merchant_id: &str, + _fingerprint_id: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn list_blocklist_entries_by_merchant_id( + &self, + _merchant_id: &str, + ) -> CustomResult, errors::StorageError> { + Err(errors::StorageError::MockDbError)? + } + + async fn list_blocklist_entries_by_merchant_id_data_kind( + &self, + _merchant_id: &str, + _data_kind: common_enums::BlocklistDataKind, + _limit: i64, + _offset: i64, + ) -> CustomResult, errors::StorageError> { + Err(errors::StorageError::MockDbError)? + } + + async fn delete_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + _merchant_id: &str, + _fingerprint_id: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} + +#[async_trait::async_trait] +impl BlocklistInterface for KafkaStore { + #[instrument(skip_all)] + async fn insert_blocklist_entry( + &self, + pm_blocklist: storage::BlocklistNew, + ) -> CustomResult { + self.diesel_store.insert_blocklist_entry(pm_blocklist).await + } + + async fn find_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + self.diesel_store + .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await + } + + async fn delete_blocklist_entry_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + self.diesel_store + .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await + } + + async fn list_blocklist_entries_by_merchant_id_data_kind( + &self, + merchant_id: &str, + data_kind: common_enums::BlocklistDataKind, + limit: i64, + offset: i64, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_blocklist_entries_by_merchant_id_data_kind(merchant_id, data_kind, limit, offset) + .await + } + + async fn list_blocklist_entries_by_merchant_id( + &self, + merchant_id: &str, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_blocklist_entries_by_merchant_id(merchant_id) + .await + } +} diff --git a/crates/router/src/db/blocklist_fingerprint.rs b/crates/router/src/db/blocklist_fingerprint.rs new file mode 100644 index 00000000000..d9107d3d1c1 --- /dev/null +++ b/crates/router/src/db/blocklist_fingerprint.rs @@ -0,0 +1,99 @@ +use error_stack::IntoReport; +use router_env::{instrument, tracing}; +use storage_impl::MockDb; + +use super::Store; +use crate::{ + connection, + core::errors::{self, CustomResult}, + db::kafka_store::KafkaStore, + types::storage, +}; + +#[async_trait::async_trait] +pub trait BlocklistFingerprintInterface { + async fn insert_blocklist_fingerprint_entry( + &self, + pm_fingerprint_new: storage::BlocklistFingerprintNew, + ) -> CustomResult; + + async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl BlocklistFingerprintInterface for Store { + #[instrument(skip_all)] + async fn insert_blocklist_fingerprint_entry( + &self, + pm_fingerprint_new: storage::BlocklistFingerprintNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + pm_fingerprint_new + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + } + + async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint_id: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::BlocklistFingerprint::find_by_merchant_id_fingerprint_id( + &conn, + merchant_id, + fingerprint_id, + ) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl BlocklistFingerprintInterface for MockDb { + #[instrument(skip_all)] + async fn insert_blocklist_fingerprint_entry( + &self, + _pm_fingerprint_new: storage::BlocklistFingerprintNew, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &self, + _merchant_id: &str, + _fingerprint_id: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} + +#[async_trait::async_trait] +impl BlocklistFingerprintInterface for KafkaStore { + #[instrument(skip_all)] + async fn insert_blocklist_fingerprint_entry( + &self, + pm_fingerprint_new: storage::BlocklistFingerprintNew, + ) -> CustomResult { + self.diesel_store + .insert_blocklist_fingerprint_entry(pm_fingerprint_new) + .await + } + + async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + self.diesel_store + .find_blocklist_fingerprint_by_merchant_id_fingerprint_id(merchant_id, fingerprint) + .await + } +} diff --git a/crates/router/src/db/blocklist_lookup.rs b/crates/router/src/db/blocklist_lookup.rs new file mode 100644 index 00000000000..f5fb4ea9ed8 --- /dev/null +++ b/crates/router/src/db/blocklist_lookup.rs @@ -0,0 +1,131 @@ +use error_stack::IntoReport; +use router_env::{instrument, tracing}; +use storage_impl::MockDb; + +use super::Store; +use crate::{ + connection, + core::errors::{self, CustomResult}, + db::kafka_store::KafkaStore, + types::storage, +}; + +#[async_trait::async_trait] +pub trait BlocklistLookupInterface { + async fn insert_blocklist_lookup_entry( + &self, + blocklist_lookup_new: storage::BlocklistLookupNew, + ) -> CustomResult; + + async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult; + + async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl BlocklistLookupInterface for Store { + #[instrument(skip_all)] + async fn insert_blocklist_lookup_entry( + &self, + blocklist_lookup_entry: storage::BlocklistLookupNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + blocklist_lookup_entry + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + } + + async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::BlocklistLookup::find_by_merchant_id_fingerprint(&conn, merchant_id, fingerprint) + .await + .map_err(Into::into) + .into_report() + } + + async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::BlocklistLookup::delete_by_merchant_id_fingerprint(&conn, merchant_id, fingerprint) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl BlocklistLookupInterface for MockDb { + #[instrument(skip_all)] + async fn insert_blocklist_lookup_entry( + &self, + _blocklist_lookup_entry: storage::BlocklistLookupNew, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + _merchant_id: &str, + _fingerprint: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + _merchant_id: &str, + _fingerprint: &str, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} + +#[async_trait::async_trait] +impl BlocklistLookupInterface for KafkaStore { + #[instrument(skip_all)] + async fn insert_blocklist_lookup_entry( + &self, + blocklist_lookup_entry: storage::BlocklistLookupNew, + ) -> CustomResult { + self.diesel_store + .insert_blocklist_lookup_entry(blocklist_lookup_entry) + .await + } + + async fn find_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + self.diesel_store + .find_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, fingerprint) + .await + } + + async fn delete_blocklist_lookup_entry_by_merchant_id_fingerprint( + &self, + merchant_id: &str, + fingerprint: &str, + ) -> CustomResult { + self.diesel_store + .delete_blocklist_lookup_entry_by_merchant_id_fingerprint(merchant_id, fingerprint) + .await + } +} diff --git a/crates/router/src/db/cache.rs b/crates/router/src/db/cache.rs index 0688665f0c4..4bda08e22c0 100644 --- a/crates/router/src/db/cache.rs +++ b/crates/router/src/db/cache.rs @@ -63,7 +63,7 @@ where F: FnOnce() -> Fut + Send, Fut: futures::Future> + Send, { - let cache_val = cache.get_val::(key); + let cache_val = cache.get_val::(key).await; if let Some(val) = cache_val { Ok(val) } else { diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs index c63585205bb..4529b121fa2 100644 --- a/crates/router/src/db/dispute.rs +++ b/crates/router/src/db/dispute.rs @@ -572,7 +572,7 @@ mod tests { assert_eq!(1, found_disputes.len()); - assert_eq!(created_dispute, found_disputes.get(0).unwrap().clone()); + assert_eq!(created_dispute, found_disputes.first().unwrap().clone()); } #[tokio::test] @@ -611,7 +611,7 @@ mod tests { assert_eq!(1, found_disputes.len()); - assert_eq!(created_dispute, found_disputes.get(0).unwrap().clone()); + assert_eq!(created_dispute, found_disputes.first().unwrap().clone()); } mod update_dispute { diff --git a/crates/router/src/db/health_check.rs b/crates/router/src/db/health_check.rs new file mode 100644 index 00000000000..73bc2a4321d --- /dev/null +++ b/crates/router/src/db/health_check.rs @@ -0,0 +1,147 @@ +use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; +use diesel_models::ConfigNew; +use error_stack::ResultExt; +use router_env::logger; + +use super::{MockDb, StorageInterface, Store}; +use crate::{ + connection, + consts::LOCKER_HEALTH_CALL_PATH, + core::errors::{self, CustomResult}, + routes, + services::api as services, + types::storage, +}; + +#[async_trait::async_trait] +pub trait HealthCheckInterface { + async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError>; + async fn health_check_redis( + &self, + db: &dyn StorageInterface, + ) -> CustomResult<(), errors::HealthCheckRedisError>; + async fn health_check_locker( + &self, + state: &routes::AppState, + ) -> CustomResult<(), errors::HealthCheckLockerError>; +} + +#[async_trait::async_trait] +impl HealthCheckInterface for Store { + async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError> { + let conn = connection::pg_connection_write(self) + .await + .change_context(errors::HealthCheckDBError::DBError)?; + + let _data = conn + .transaction_async(|conn| { + Box::pin(async move { + let query = + diesel::select(diesel::dsl::sql::("1 + 1")); + let _x: i32 = query.get_result_async(&conn).await.map_err(|err| { + logger::error!(read_err=?err,"Error while reading element in the database"); + errors::HealthCheckDBError::DBReadError + })?; + + logger::debug!("Database read was successful"); + + let config = ConfigNew { + key: "test_key".to_string(), + config: "test_value".to_string(), + }; + + config.insert(&conn).await.map_err(|err| { + logger::error!(write_err=?err,"Error while writing to database"); + errors::HealthCheckDBError::DBWriteError + })?; + + logger::debug!("Database write was successful"); + + storage::Config::delete_by_key(&conn, "test_key").await.map_err(|err| { + logger::error!(delete_err=?err,"Error while deleting element in the database"); + errors::HealthCheckDBError::DBDeleteError + })?; + + logger::debug!("Database delete was successful"); + + Ok::<_, errors::HealthCheckDBError>(()) + }) + }) + .await?; + + Ok(()) + } + + async fn health_check_redis( + &self, + db: &dyn StorageInterface, + ) -> CustomResult<(), errors::HealthCheckRedisError> { + let redis_conn = db + .get_redis_conn() + .change_context(errors::HealthCheckRedisError::RedisConnectionError)?; + + redis_conn + .serialize_and_set_key_with_expiry("test_key", "test_value", 30) + .await + .change_context(errors::HealthCheckRedisError::SetFailed)?; + + logger::debug!("Redis set_key was successful"); + + redis_conn + .get_key("test_key") + .await + .change_context(errors::HealthCheckRedisError::GetFailed)?; + + logger::debug!("Redis get_key was successful"); + + redis_conn + .delete_key("test_key") + .await + .change_context(errors::HealthCheckRedisError::DeleteFailed)?; + + logger::debug!("Redis delete_key was successful"); + + Ok(()) + } + + async fn health_check_locker( + &self, + state: &routes::AppState, + ) -> CustomResult<(), errors::HealthCheckLockerError> { + let locker = &state.conf.locker; + if !locker.mock_locker { + let mut url = locker.host_rs.to_owned(); + url.push_str(LOCKER_HEALTH_CALL_PATH); + let request = services::Request::new(services::Method::Get, &url); + services::call_connector_api(state, request) + .await + .change_context(errors::HealthCheckLockerError::FailedToCallLocker)? + .ok(); + } + + logger::debug!("Locker call was successful"); + + Ok(()) + } +} + +#[async_trait::async_trait] +impl HealthCheckInterface for MockDb { + async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError> { + Ok(()) + } + + async fn health_check_redis( + &self, + _: &dyn StorageInterface, + ) -> CustomResult<(), errors::HealthCheckRedisError> { + Ok(()) + } + + async fn health_check_locker( + &self, + _: &routes::AppState, + ) -> CustomResult<(), errors::HealthCheckLockerError> { + Ok(()) + } +} diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index db94c1bcbca..19a83088a06 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -43,6 +43,7 @@ use crate::{ events::EventInterface, file::FileMetadataInterface, gsm::GsmInterface, + health_check::HealthCheckInterface, locker_mock_up::LockerMockUpInterface, mandate::MandateInterface, merchant_account::MerchantAccountInterface, @@ -57,6 +58,7 @@ use crate::{ routing_algorithm::RoutingAlgorithmInterface, MasterKeyInterface, StorageInterface, }, + routes, services::{authentication, kafka::KafkaProducer, Store}, types::{ domain, @@ -660,6 +662,16 @@ impl MerchantAccountInterface for KafkaStore { .delete_merchant_account_by_merchant_id(merchant_id) .await } + + #[cfg(feature = "olap")] + async fn list_multiple_merchant_accounts( + &self, + merchant_ids: Vec, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_multiple_merchant_accounts(merchant_ids) + .await + } } #[async_trait::async_trait] @@ -1613,6 +1625,17 @@ impl MerchantKeyStoreInterface for KafkaStore { .delete_merchant_key_store_by_merchant_id(merchant_id) .await } + + #[cfg(feature = "olap")] + async fn list_multiple_key_stores( + &self, + merchant_ids: Vec, + key: &Secret>, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_multiple_key_stores(merchant_ids, key) + .await + } } #[async_trait::async_trait] @@ -2131,3 +2154,24 @@ impl AuthorizationInterface for KafkaStore { .await } } + +#[async_trait::async_trait] +impl HealthCheckInterface for KafkaStore { + async fn health_check_db(&self) -> CustomResult<(), errors::HealthCheckDBError> { + self.diesel_store.health_check_db().await + } + + async fn health_check_redis( + &self, + db: &dyn StorageInterface, + ) -> CustomResult<(), errors::HealthCheckRedisError> { + self.diesel_store.health_check_redis(db).await + } + + async fn health_check_locker( + &self, + state: &routes::AppState, + ) -> CustomResult<(), errors::HealthCheckLockerError> { + self.diesel_store.health_check_locker(state).await + } +} diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index 0d3ce99b948..70d417c0366 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "olap")] +use std::collections::HashMap; + use common_utils::ext_traits::AsyncExt; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "accounts_cache")] @@ -65,6 +68,12 @@ where &self, merchant_id: &str, ) -> CustomResult; + + #[cfg(feature = "olap")] + async fn list_multiple_merchant_accounts( + &self, + merchant_ids: Vec, + ) -> CustomResult, errors::StorageError>; } #[async_trait::async_trait] @@ -294,6 +303,57 @@ impl MerchantAccountInterface for Store { Ok(is_deleted) } + + #[cfg(feature = "olap")] + async fn list_multiple_merchant_accounts( + &self, + merchant_ids: Vec, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + + let encrypted_merchant_accounts = + storage::MerchantAccount::list_multiple_merchant_accounts(&conn, merchant_ids) + .await + .map_err(Into::into) + .into_report()?; + + let db_master_key = self.get_master_key().to_vec().into(); + + let merchant_key_stores = self + .list_multiple_key_stores( + encrypted_merchant_accounts + .iter() + .map(|merchant_account| &merchant_account.merchant_id) + .cloned() + .collect(), + &db_master_key, + ) + .await?; + + let key_stores_by_id: HashMap<_, _> = merchant_key_stores + .iter() + .map(|key_store| (key_store.merchant_id.to_owned(), key_store)) + .collect(); + + let merchant_accounts = + futures::future::try_join_all(encrypted_merchant_accounts.into_iter().map( + |merchant_account| async { + let key_store = key_stores_by_id.get(&merchant_account.merchant_id).ok_or( + errors::StorageError::ValueNotFound(format!( + "merchant_key_store with merchant_id = {}", + merchant_account.merchant_id + )), + )?; + merchant_account + .convert(key_store.key.get_inner()) + .await + .change_context(errors::StorageError::DecryptionError) + }, + )) + .await?; + + Ok(merchant_accounts) + } } #[async_trait::async_trait] @@ -392,6 +452,14 @@ impl MerchantAccountInterface for MockDb { ) -> CustomResult, errors::StorageError> { Err(errors::StorageError::MockDbError)? } + + #[cfg(feature = "olap")] + async fn list_multiple_merchant_accounts( + &self, + _merchant_ids: Vec, + ) -> CustomResult, errors::StorageError> { + Err(errors::StorageError::MockDbError)? + } } #[cfg(feature = "accounts_cache")] diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 4fbb8f19ccf..3718b962b45 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -890,6 +890,7 @@ mod merchant_connector_account_cache_tests { "{}_{}", merchant_id, connector_label ),) + .await .is_none()) } } diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 970e2b77032..630b833afdd 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -32,6 +32,13 @@ pub trait MerchantKeyStoreInterface { &self, merchant_id: &str, ) -> CustomResult; + + #[cfg(feature = "olap")] + async fn list_multiple_key_stores( + &self, + merchant_ids: Vec, + key: &Secret>, + ) -> CustomResult, errors::StorageError>; } #[async_trait::async_trait] @@ -128,6 +135,33 @@ impl MerchantKeyStoreInterface for Store { .await } } + + #[cfg(feature = "olap")] + async fn list_multiple_key_stores( + &self, + merchant_ids: Vec, + key: &Secret>, + ) -> CustomResult, errors::StorageError> { + let fetch_func = || async { + let conn = connection::pg_connection_read(self).await?; + + diesel_models::merchant_key_store::MerchantKeyStore::list_multiple_key_stores( + &conn, + merchant_ids, + ) + .await + .map_err(Into::into) + .into_report() + }; + + futures::future::try_join_all(fetch_func().await?.into_iter().map(|key_store| async { + key_store + .convert(key) + .await + .change_context(errors::StorageError::DecryptionError) + })) + .await + } } #[async_trait::async_trait] @@ -194,6 +228,28 @@ impl MerchantKeyStoreInterface for MockDb { merchant_key_stores.remove(index); Ok(true) } + + #[cfg(feature = "olap")] + async fn list_multiple_key_stores( + &self, + merchant_ids: Vec, + key: &Secret>, + ) -> CustomResult, errors::StorageError> { + let merchant_key_stores = self.merchant_key_store.lock().await; + futures::future::try_join_all( + merchant_key_stores + .iter() + .filter(|merchant_key| merchant_ids.contains(&merchant_key.merchant_id)) + .map(|merchant_key| async { + merchant_key + .to_owned() + .convert(key) + .await + .change_context(errors::StorageError::DecryptionError) + }), + ) + .await + } } #[cfg(test)] diff --git a/crates/router/src/db/payment_link.rs b/crates/router/src/db/payment_link.rs index 5dc9871e707..a56f9dcce2e 100644 --- a/crates/router/src/db/payment_link.rs +++ b/crates/router/src/db/payment_link.rs @@ -42,10 +42,10 @@ impl PaymentLinkInterface for Store { async fn insert_payment_link( &self, - payment_link_object: storage::PaymentLinkNew, + payment_link_config: storage::PaymentLinkNew, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - payment_link_object + payment_link_config .insert(&conn) .await .map_err(Into::into) diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index 37e38e8afca..bf84ae134ea 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -123,7 +123,7 @@ impl UserRoleInterface for MockDb { status: user_role.status, created_by: user_role.created_by, created_at: user_role.created_at, - last_modified_at: user_role.last_modified_at, + last_modified: user_role.last_modified, last_modified_by: user_role.last_modified_by, org_id: user_role.org_id, }; diff --git a/crates/router/src/events.rs b/crates/router/src/events.rs index 8f980fee504..0091de588f1 100644 --- a/crates/router/src/events.rs +++ b/crates/router/src/events.rs @@ -6,8 +6,10 @@ use storage_impl::errors::ApplicationError; use crate::{db::KafkaProducer, services::kafka::KafkaSettings}; pub mod api_logs; +pub mod connector_api_logs; pub mod event_logger; pub mod kafka_handler; +pub mod outgoing_webhook_logs; pub(super) trait EventHandler: Sync + Send + dyn_clone::DynClone { fn log_event(&self, event: RawEvent); @@ -29,6 +31,8 @@ pub enum EventType { PaymentAttempt, Refund, ApiLogs, + ConnectorApiLogs, + OutgoingWebhookLogs, } #[derive(Debug, Default, Deserialize, Clone)] diff --git a/crates/router/src/events/api_logs.rs b/crates/router/src/events/api_logs.rs index bfc10f722c1..78a66d2f04e 100644 --- a/crates/router/src/events/api_logs.rs +++ b/crates/router/src/events/api_logs.rs @@ -41,7 +41,7 @@ pub struct ApiEvent { #[serde(flatten)] event_type: ApiEventsType, hs_latency: Option, - http_method: Option, + http_method: String, } impl ApiEvent { @@ -59,7 +59,7 @@ impl ApiEvent { error: Option, event_type: ApiEventsType, http_req: &HttpRequest, - http_method: Option, + http_method: &http::Method, ) -> Self { Self { merchant_id, @@ -83,7 +83,7 @@ impl ApiEvent { url_path: http_req.path().to_string(), event_type, hs_latency, - http_method, + http_method: http_method.to_string(), } } } @@ -116,7 +116,6 @@ impl_misc_api_event_type!( AttachEvidenceRequest, DisputeId, PaymentLinkFormData, - PaymentsRedirectResponseData, ConfigUpdate ); @@ -131,3 +130,15 @@ impl_misc_api_event_type!( DummyConnectorRefundResponse, DummyConnectorRefundRequest ); + +impl ApiEventMetric for PaymentsRedirectResponseData { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentRedirectionResponse { + connector: self.connector.clone(), + payment_id: match &self.resource_id { + api_models::payments::PaymentIdType::PaymentIntentId(id) => Some(id.clone()), + _ => None, + }, + }) + } +} diff --git a/crates/router/src/events/connector_api_logs.rs b/crates/router/src/events/connector_api_logs.rs new file mode 100644 index 00000000000..871a7af0d77 --- /dev/null +++ b/crates/router/src/events/connector_api_logs.rs @@ -0,0 +1,69 @@ +use common_utils::request::Method; +use router_env::tracing_actix_web::RequestId; +use serde::Serialize; +use time::OffsetDateTime; + +use super::{EventType, RawEvent}; + +#[derive(Debug, Serialize)] +pub struct ConnectorEvent { + connector_name: String, + flow: String, + request: String, + response: Option, + url: String, + method: String, + payment_id: String, + merchant_id: String, + created_at: i128, + request_id: String, + latency: u128, +} + +impl ConnectorEvent { + #[allow(clippy::too_many_arguments)] + pub fn new( + connector_name: String, + flow: &str, + request: serde_json::Value, + response: Option, + url: String, + method: Method, + payment_id: String, + merchant_id: String, + request_id: Option<&RequestId>, + latency: u128, + ) -> Self { + Self { + connector_name, + flow: flow + .rsplit_once("::") + .map(|(_, s)| s) + .unwrap_or(flow) + .to_string(), + request: request.to_string(), + response, + url, + method: method.to_string(), + payment_id, + merchant_id, + created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000, + request_id: request_id + .map(|i| i.as_hyphenated().to_string()) + .unwrap_or("NO_REQUEST_ID".to_string()), + latency, + } + } +} + +impl TryFrom for RawEvent { + type Error = serde_json::Error; + + fn try_from(value: ConnectorEvent) -> Result { + Ok(Self { + event_type: EventType::ConnectorApiLogs, + key: value.request_id.clone(), + payload: serde_json::to_value(value)?, + }) + } +} diff --git a/crates/router/src/events/outgoing_webhook_logs.rs b/crates/router/src/events/outgoing_webhook_logs.rs new file mode 100644 index 00000000000..ebf36caf706 --- /dev/null +++ b/crates/router/src/events/outgoing_webhook_logs.rs @@ -0,0 +1,110 @@ +use api_models::{enums::EventType as OutgoingWebhookEventType, webhooks::OutgoingWebhookContent}; +use serde::Serialize; +use serde_json::Value; +use time::OffsetDateTime; + +use super::{EventType, RawEvent}; + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct OutgoingWebhookEvent { + merchant_id: String, + event_id: String, + event_type: OutgoingWebhookEventType, + #[serde(flatten)] + content: Option, + is_error: bool, + error: Option, + created_at_timestamp: i128, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(tag = "outgoing_webhook_event_type", rename_all = "snake_case")] +pub enum OutgoingWebhookEventContent { + Payment { + payment_id: Option, + content: Value, + }, + Refund { + payment_id: String, + refund_id: String, + content: Value, + }, + Dispute { + payment_id: String, + attempt_id: String, + dispute_id: String, + content: Value, + }, + Mandate { + payment_method_id: String, + mandate_id: String, + content: Value, + }, +} +pub trait OutgoingWebhookEventMetric { + fn get_outgoing_webhook_event_type(&self) -> Option; +} +impl OutgoingWebhookEventMetric for OutgoingWebhookContent { + fn get_outgoing_webhook_event_type(&self) -> Option { + match self { + Self::PaymentDetails(payment_payload) => Some(OutgoingWebhookEventContent::Payment { + payment_id: payment_payload.payment_id.clone(), + content: masking::masked_serialize(&payment_payload) + .unwrap_or(serde_json::json!({"error":"failed to serialize"})), + }), + Self::RefundDetails(refund_payload) => Some(OutgoingWebhookEventContent::Refund { + payment_id: refund_payload.payment_id.clone(), + refund_id: refund_payload.refund_id.clone(), + content: masking::masked_serialize(&refund_payload) + .unwrap_or(serde_json::json!({"error":"failed to serialize"})), + }), + Self::DisputeDetails(dispute_payload) => Some(OutgoingWebhookEventContent::Dispute { + payment_id: dispute_payload.payment_id.clone(), + attempt_id: dispute_payload.attempt_id.clone(), + dispute_id: dispute_payload.dispute_id.clone(), + content: masking::masked_serialize(&dispute_payload) + .unwrap_or(serde_json::json!({"error":"failed to serialize"})), + }), + Self::MandateDetails(mandate_payload) => Some(OutgoingWebhookEventContent::Mandate { + payment_method_id: mandate_payload.payment_method_id.clone(), + mandate_id: mandate_payload.mandate_id.clone(), + content: masking::masked_serialize(&mandate_payload) + .unwrap_or(serde_json::json!({"error":"failed to serialize"})), + }), + } + } +} + +impl OutgoingWebhookEvent { + pub fn new( + merchant_id: String, + event_id: String, + event_type: OutgoingWebhookEventType, + content: Option, + is_error: bool, + error: Option, + ) -> Self { + Self { + merchant_id, + event_id, + event_type, + content, + is_error, + error, + created_at_timestamp: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000, + } + } +} + +impl TryFrom for RawEvent { + type Error = serde_json::Error; + + fn try_from(value: OutgoingWebhookEvent) -> Result { + Ok(Self { + event_type: EventType::OutgoingWebhookLogs, + key: value.merchant_id.clone(), + payload: serde_json::to_value(value)?, + }) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 3b4c7ce9b7d..c38a4dc85b5 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -129,9 +129,9 @@ pub fn mk_app( #[cfg(feature = "oltp")] { server_app = server_app - .service(routes::PaymentMethods::server(state.clone())) .service(routes::EphemeralKey::server(state.clone())) .service(routes::Webhooks::server(state.clone())) + .service(routes::PaymentMethods::server(state.clone())) } #[cfg(feature = "olap")] @@ -143,6 +143,7 @@ pub fn mk_app( .service(routes::Disputes::server(state.clone())) .service(routes::Analytics::server(state.clone())) .service(routes::Routing::server(state.clone())) + .service(routes::Blocklist::server(state.clone())) .service(routes::LockerMigrate::server(state.clone())) .service(routes::Gsm::server(state.clone())) .service(routes::PaymentLink::server(state.clone())) @@ -164,6 +165,12 @@ pub fn mk_app( { server_app = server_app.service(routes::StripeApis::server(state.clone())); } + + #[cfg(feature = "recon")] + { + server_app = server_app.service(routes::Recon::server(state.clone())); + } + server_app = server_app.service(routes::Cards::server(state.clone())); server_app = server_app.service(routes::Cache::server(state.clone())); server_app = server_app.service(routes::Health::server(state)); diff --git a/crates/router/src/macros.rs b/crates/router/src/macros.rs index e6c9dba7d6e..efe71e49bb0 100644 --- a/crates/router/src/macros.rs +++ b/crates/router/src/macros.rs @@ -1,4 +1 @@ -pub use common_utils::{ - async_spawn, collect_missing_value_keys, fallback_reverse_lookup_not_found, newtype, - newtype_impl, -}; +pub use common_utils::{collect_missing_value_keys, newtype}; diff --git a/crates/router/src/middleware.rs b/crates/router/src/middleware.rs index 1576432e26e..c6c94d3a78e 100644 --- a/crates/router/src/middleware.rs +++ b/crates/router/src/middleware.rs @@ -43,16 +43,21 @@ where actix_web::dev::forward_ready!(service); fn call(&self, req: actix_web::dev::ServiceRequest) -> Self::Future { + let old_x_request_id = req.headers().get("x-request-id").cloned(); let mut req = req; let request_id_fut = req.extract::(); let response_fut = self.service.call(req); Box::pin(async move { let request_id = request_id_fut.await?; + let request_id = request_id.as_hyphenated().to_string(); + if let Some(upstream_request_id) = old_x_request_id { + router_env::logger::info!(?request_id, ?upstream_request_id); + } let mut response = response_fut.await?; response.headers_mut().append( http::header::HeaderName::from_static("x-request-id"), - http::HeaderValue::from_str(&request_id.as_hyphenated().to_string())?, + http::HeaderValue::from_str(&request_id)?, ); Ok(response) @@ -65,7 +70,13 @@ where pub fn default_response_headers() -> actix_web::middleware::DefaultHeaders { use actix_web::http::header; - actix_web::middleware::DefaultHeaders::new() + let default_headers_middleware = actix_web::middleware::DefaultHeaders::new(); + + #[cfg(feature = "vergen")] + let default_headers_middleware = + default_headers_middleware.add(("x-hyperswitch-version", router_env::git_tag!())); + + default_headers_middleware // Max age of 1 year in seconds, equal to `60 * 60 * 24 * 365` seconds. .add((header::STRICT_TRANSPORT_SECURITY, "max-age=31536000")) .add((header::VIA, "HyperSwitch")) diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 95c36719cad..174926c7d36 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -119,6 +119,9 @@ Never share your secret api keys. Keep them guarded and secure. crate::routes::gsm::get_gsm_rule, crate::routes::gsm::update_gsm_rule, crate::routes::gsm::delete_gsm_rule, + crate::routes::blocklist::add_entry_to_blocklist, + crate::routes::blocklist::list_blocked_payment_methods, + crate::routes::blocklist::remove_entry_from_blocklist ), components(schemas( crate::types::api::refunds::RefundRequest, @@ -186,8 +189,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::MerchantConnectorDetailsWrap, api_models::admin::MerchantConnectorDetails, api_models::admin::MerchantConnectorWebhookDetails, + api_models::admin::BusinessPaymentLinkConfig, + api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfig, - api_models::admin::PaymentLinkColorSchema, api_models::disputes::DisputeResponse, api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::gsm::GsmCreateRequest, @@ -268,6 +272,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SecretInfoToInitiateSdk, api_models::payments::ApplePayPaymentRequest, api_models::payments::AmountInfo, + api_models::payments::ProductType, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, api_models::payments::PaypalRedirection, @@ -317,6 +322,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentAttemptResponse, api_models::payments::CaptureResponse, api_models::payments::IncrementalAuthorizationResponse, + api_models::payments::PaymentCreatePaymentLinkConfig, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::MaskedBankDetails, api_models::payment_methods::SurchargeDetailsResponse, @@ -367,7 +373,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentLinkResponse, api_models::payments::RetrievePaymentLinkResponse, api_models::payments::PaymentLinkInitiateRequest, - api_models::payments::PaymentLinkObject, + api_models::payments::PaymentLinkStatus, + api_models::blocklist::BlocklistRequest, + api_models::blocklist::BlocklistResponse, + api_models::blocklist::ListBlocklistQuery, + common_enums::enums::BlocklistDataKind )), modifiers(&SecurityAddon) )] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index ec718b2dde9..d9916f98e74 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -1,6 +1,8 @@ pub mod admin; pub mod api_keys; pub mod app; +#[cfg(feature = "olap")] +pub mod blocklist; pub mod cache; pub mod cards_info; pub mod configs; @@ -26,6 +28,8 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +#[cfg(feature = "recon")] +pub mod recon; pub mod refunds; #[cfg(feature = "olap")] pub mod routing; @@ -42,14 +46,17 @@ pub mod webhooks; pub mod locker_migration; #[cfg(any(feature = "olap", feature = "oltp"))] pub mod pm_auth; +#[cfg(feature = "olap")] +pub use app::{Blocklist, Routing}; + #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; #[cfg(any(feature = "olap", feature = "oltp"))] pub use self::app::Forex; #[cfg(feature = "payouts")] pub use self::app::Payouts; -#[cfg(feature = "olap")] -pub use self::app::Routing; +#[cfg(all(feature = "olap", feature = "recon"))] +pub use self::app::Recon; #[cfg(all(feature = "olap", feature = "kms"))] pub use self::app::Verify; pub use self::app::{ diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index ce6a2a97e28..ab404125a38 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -360,7 +360,7 @@ pub async fn payment_connector_update( let flow = Flow::MerchantConnectorsUpdate; let (merchant_id, merchant_connector_id) = path.into_inner(); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -375,7 +375,7 @@ pub async fn payment_connector_update( req.headers(), ), api_locking::LockAction::NotApplicable, - ) + )) .await } /// Merchant Connector - Delete diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs index 5b4c047b146..9293d6e1143 100644 --- a/crates/router/src/routes/api_keys.rs +++ b/crates/router/src/routes/api_keys.rs @@ -140,7 +140,7 @@ pub async fn api_key_update( let (merchant_id, key_id) = path.into_inner(); let mut payload = json_payload.into_inner(); payload.key_id = key_id; - payload.merchant_id = merchant_id; + payload.merchant_id = merchant_id.clone(); api::server_wrap( flow, @@ -148,7 +148,14 @@ pub async fn api_key_update( &req, payload, |state, _, payload| api_keys::update_api_key(state, payload), - &auth::AdminApiAuth, + auth::auth_type( + &auth::AdminApiAuth, + &auth::JWTAuthMerchantFromRoute { + merchant_id, + required_permission: Permission::ApiKeyWrite, + }, + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 34ae6fa3ecb..0c489dbe63a 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -14,6 +14,8 @@ use scheduler::SchedulerInterface; use storage_impl::MockDb; use tokio::sync::oneshot; +#[cfg(feature = "olap")] +use super::blocklist; #[cfg(any(feature = "olap", feature = "oltp"))] use super::currency; #[cfg(feature = "dummy_connector")] @@ -38,6 +40,8 @@ use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; use super::{ephemeral_key::*, payment_methods::*, webhooks::*}; #[cfg(all(feature = "frm", feature = "oltp"))] use crate::routes::fraud_check as frm_routes; +#[cfg(all(feature = "recon", feature = "olap"))] +use crate::routes::recon as recon_routes; #[cfg(feature = "olap")] use crate::routes::verify_connector::payment_connector_verify; pub use crate::{ @@ -61,6 +65,7 @@ pub struct AppState { pub api_client: Box, #[cfg(feature = "olap")] pub pool: crate::analytics::AnalyticsProvider, + pub request_id: Option, } impl scheduler::SchedulerAppState for AppState { @@ -97,7 +102,8 @@ impl AppStateInfo for AppState { } fn add_request_id(&mut self, request_id: RequestId) { self.api_client.add_request_id(request_id); - self.store.add_request_id(request_id.to_string()) + self.store.add_request_id(request_id.to_string()); + self.request_id.replace(request_id); } fn add_merchant_id(&mut self, merchant_id: Option) { @@ -226,6 +232,7 @@ impl AppState { event_handler, #[cfg(feature = "olap")] pool, + request_id: None, } }) .await @@ -250,9 +257,10 @@ pub struct Health; impl Health { pub fn server(state: AppState) -> Scope { - web::scope("") + web::scope("health") .app_data(web::Data::new(state)) - .service(web::resource("/health").route(web::get().to(health))) + .service(web::resource("").route(web::get().to(health))) + .service(web::resource("/deep_check").route(web::post().to(deep_health_check))) } } @@ -562,6 +570,43 @@ impl PaymentMethods { } } +#[cfg(all(feature = "olap", feature = "recon"))] +pub struct Recon; + +#[cfg(all(feature = "olap", feature = "recon"))] +impl Recon { + pub fn server(state: AppState) -> Scope { + web::scope("/recon") + .app_data(web::Data::new(state)) + .service( + web::resource("/update_merchant") + .route(web::post().to(recon_routes::update_merchant)), + ) + .service(web::resource("/token").route(web::get().to(recon_routes::get_recon_token))) + .service( + web::resource("/request").route(web::post().to(recon_routes::request_for_recon)), + ) + .service(web::resource("/verify_token").route(web::get().to(verify_recon_token))) + } +} + +#[cfg(feature = "olap")] +pub struct Blocklist; + +#[cfg(feature = "olap")] +impl Blocklist { + pub fn server(state: AppState) -> Scope { + web::scope("/blocklist") + .app_data(web::Data::new(state)) + .service( + web::resource("") + .route(web::get().to(blocklist::list_blocked_payment_methods)) + .route(web::post().to(blocklist::add_entry_to_blocklist)) + .route(web::delete().to(blocklist::remove_entry_from_blocklist)), + ) + } +} + pub struct MerchantAccount; #[cfg(feature = "olap")] @@ -875,6 +920,7 @@ impl User { .service(web::resource("/user/update_role").route(web::post().to(update_user_role))) .service(web::resource("/role/list").route(web::get().to(list_roles))) .service(web::resource("/role/{role_id}").route(web::get().to(get_role))) + .service(web::resource("/user/invite").route(web::post().to(invite_user))) .service( web::resource("/data") .route(web::get().to(get_multiple_dashboard_metadata)) @@ -897,12 +943,15 @@ impl User { ) .service(web::resource("/forgot_password").route(web::post().to(forgot_password))) .service(web::resource("/reset_password").route(web::post().to(reset_password))) - .service(web::resource("user/invite").route(web::post().to(invite_user))) .service( web::resource("/signup_with_merchant_id") .route(web::post().to(user_signup_with_merchant_id)), ) .service(web::resource("/verify_email").route(web::post().to(verify_email))) + .service( + web::resource("/verify_email_request") + .route(web::post().to(verify_email_request)), + ); } #[cfg(not(feature = "email"))] { @@ -934,5 +983,6 @@ impl ConnectorOnboarding { .app_data(web::Data::new(state)) .service(web::resource("/action_url").route(web::post().to(get_action_url))) .service(web::resource("/sync").route(web::post().to(sync_onboarding_status))) + .service(web::resource("/reset_tracking_id").route(web::post().to(reset_tracking_id))) } } diff --git a/crates/router/src/routes/blocklist.rs b/crates/router/src/routes/blocklist.rs new file mode 100644 index 00000000000..9c93f49ab83 --- /dev/null +++ b/crates/router/src/routes/blocklist.rs @@ -0,0 +1,119 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::blocklist as api_blocklist; +use router_env::Flow; + +use crate::{ + core::{api_locking, blocklist}, + routes::AppState, + services::{api, authentication as auth, authorization::permissions::Permission}, +}; + +#[utoipa::path( + post, + path = "/blocklist", + request_body = BlocklistRequest, + responses( + (status = 200, description = "Fingerprint Blocked", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Block a Fingerprint", + security(("api_key" = [])) +)] +pub async fn add_entry_to_blocklist( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::AddToBlocklist; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationData, body| { + blocklist::add_entry_to_blocklist(state, auth.merchant_account, body) + }, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountWrite), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[utoipa::path( + delete, + path = "/blocklist", + request_body = BlocklistRequest, + responses( + (status = 200, description = "Fingerprint Unblocked", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "Unblock a Fingerprint", + security(("api_key" = [])) +)] +pub async fn remove_entry_from_blocklist( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::DeleteFromBlocklist; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationData, body| { + blocklist::remove_entry_from_blocklist(state, auth.merchant_account, body) + }, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountWrite), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[utoipa::path( + get, + path = "/blocklist", + params ( + ("data_kind" = BlocklistDataKind, Query, description = "Kind of the fingerprint list requested"), + ), + responses( + (status = 200, description = "Blocked Fingerprints", body = BlocklistResponse), + (status = 400, description = "Invalid Data") + ), + tag = "Blocklist", + operation_id = "List Blocked fingerprints of a particular kind", + security(("api_key" = [])) +)] +pub async fn list_blocked_payment_methods( + state: web::Data, + req: HttpRequest, + query_payload: web::Query, +) -> HttpResponse { + let flow = Flow::ListBlocklist; + Box::pin(api::server_wrap( + flow, + state, + &req, + query_payload.into_inner(), + |state, auth: auth::AuthenticationData, query| { + blocklist::list_blocklist_entries(state, auth.merchant_account, query) + }, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MerchantAccountRead), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/connector_onboarding.rs b/crates/router/src/routes/connector_onboarding.rs index b7c39b3c1d2..f5555f5bf9b 100644 --- a/crates/router/src/routes/connector_onboarding.rs +++ b/crates/router/src/routes/connector_onboarding.rs @@ -20,7 +20,7 @@ pub async fn get_action_url( state, &http_req, req_payload.clone(), - |state, _: auth::UserFromToken, req| core::get_action_url(state, req), + core::get_action_url, &auth::JWTAuth(Permission::MerchantAccountWrite), api_locking::LockAction::NotApplicable, )) @@ -45,3 +45,22 @@ pub async fn sync_onboarding_status( )) .await } + +pub async fn reset_tracking_id( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ResetTrackingId; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + core::reset_tracking_id, + &auth::JWTAuth(Permission::MerchantAccountWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index cfc37cbdbb2..2592d8837d5 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, customers::*}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api::customers, }; @@ -36,7 +36,11 @@ pub async fn customers_create( &req, json_payload.into_inner(), |state, auth, req| create_customer(state, auth.merchant_account, auth.key_store, req), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -68,11 +72,14 @@ pub async fn customers_retrieve( }) .into_inner(); - let auth = + let auth = if auth::is_jwt_auth(req.headers()) { + Box::new(auth::JWTAuth(Permission::CustomerRead)) + } else { match auth::is_ephemeral_auth(req.headers(), &*state.store, &payload.customer_id).await { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), - }; + } + }; api::server_wrap( flow, @@ -110,7 +117,11 @@ pub async fn customers_list(state: web::Data, req: HttpRequest) -> Htt &req, (), |state, auth, _| list_customers(state, auth.merchant_account.merchant_id, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -148,7 +159,11 @@ pub async fn customers_update( &req, json_payload.into_inner(), |state, auth, req| update_customer(state, auth.merchant_account, req, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -185,7 +200,11 @@ pub async fn customers_delete( &req, payload, |state, auth, req| delete_customer(state, auth.merchant_account, req, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -209,7 +228,11 @@ pub async fn get_customer_mandates( |state, auth, req| { crate::core::mandate::get_customer_mandates(state, auth.merchant_account, req) }, - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MandateRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/routes/dummy_connector/errors.rs b/crates/router/src/routes/dummy_connector/errors.rs index 4501df0a0fa..0b4affa8b61 100644 --- a/crates/router/src/routes/dummy_connector/errors.rs +++ b/crates/router/src/routes/dummy_connector/errors.rs @@ -20,7 +20,7 @@ pub enum DummyConnectorErrors { #[error(error_type = ErrorType::InvalidRequestError, code = "DC_02", message = "Missing required param: {field_name}")] MissingRequiredField { field_name: &'static str }, - #[error(error_type = ErrorType::InvalidRequestError, code = "DC_03", message = "Refund amount exceeds the payment amount")] + #[error(error_type = ErrorType::InvalidRequestError, code = "DC_03", message = "The refund amount exceeds the amount captured")] RefundAmountExceedsPaymentAmount, #[error(error_type = ErrorType::InvalidRequestError, code = "DC_04", message = "Card not supported. Please use test cards")] diff --git a/crates/router/src/routes/health.rs b/crates/router/src/routes/health.rs index 7c7f29bd181..f07b744f7f5 100644 --- a/crates/router/src/routes/health.rs +++ b/crates/router/src/routes/health.rs @@ -1,7 +1,9 @@ +use actix_web::web; +use api_models::health_check::RouterHealthCheckResponse; use router_env::{instrument, logger, tracing}; -use crate::routes::metrics; - +use super::app; +use crate::{routes::metrics, services}; /// . // #[logger::instrument(skip_all, name = "name1", level = "warn", fields( key1 = "val1" ))] #[instrument(skip_all)] @@ -11,3 +13,59 @@ pub async fn health() -> impl actix_web::Responder { logger::info!("Health was called"); actix_web::HttpResponse::Ok().body("health is good") } + +#[instrument(skip_all)] +pub async fn deep_health_check(state: web::Data) -> impl actix_web::Responder { + metrics::HEALTH_METRIC.add(&metrics::CONTEXT, 1, &[]); + let db = &*state.store; + let mut status_code = 200; + logger::info!("Deep health check was called"); + + logger::debug!("Database health check begin"); + + let db_status = match db.health_check_db().await { + Ok(_) => "Health is good".to_string(), + Err(err) => { + status_code = 500; + err.to_string() + } + }; + logger::debug!("Database health check end"); + + logger::debug!("Redis health check begin"); + + let redis_status = match db.health_check_redis(db).await { + Ok(_) => "Health is good".to_string(), + Err(err) => { + status_code = 500; + err.to_string() + } + }; + + logger::debug!("Redis health check end"); + + logger::debug!("Locker health check begin"); + + let locker_status = match db.health_check_locker(&state).await { + Ok(_) => "Health is good".to_string(), + Err(err) => { + status_code = 500; + err.to_string() + } + }; + + logger::debug!("Locker health check end"); + + let response = serde_json::to_string(&RouterHealthCheckResponse { + database: db_status, + redis: redis_status, + locker: locker_status, + }) + .unwrap_or_default(); + + if status_code == 200 { + services::http_response_json(response) + } else { + services::http_server_error_json_response(response) + } +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index f5519b96037..12cf76be475 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -24,12 +24,14 @@ pub enum ApiIdentifier { ApiKeys, PaymentLink, Routing, + Blocklist, Forex, RustLockerMigration, Gsm, User, UserRole, ConnectorOnboarding, + Recon, } impl From for ApiIdentifier { @@ -57,6 +59,10 @@ impl From for ApiIdentifier { Flow::RetrieveForexFlow => Self::Forex, + Flow::AddToBlocklist => Self::Blocklist, + Flow::DeleteFromBlocklist => Self::Blocklist, + Flow::ListBlocklist => Self::Blocklist, + Flow::MerchantConnectorsCreate | Flow::MerchantConnectorsRetrieve | Flow::MerchantConnectorsUpdate @@ -171,13 +177,21 @@ impl From for ApiIdentifier { | Flow::ResetPassword | Flow::InviteUser | Flow::UserSignUpWithMerchantId - | Flow::VerifyEmail => Self::User, + | Flow::VerifyEmail + | Flow::VerifyEmailRequest => Self::User, Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => { Self::UserRole } - Flow::GetActionUrl | Flow::SyncOnboardingStatus => Self::ConnectorOnboarding, + Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => { + Self::ConnectorOnboarding + } + + Flow::ReconMerchantUpdate + | Flow::ReconTokenRequest + | Flow::ReconServiceRequest + | Flow::ReconVerifyToken => Self::Recon, } } } diff --git a/crates/router/src/routes/locker_migration.rs b/crates/router/src/routes/locker_migration.rs index 892dc5941bd..a3df0c3a229 100644 --- a/crates/router/src/routes/locker_migration.rs +++ b/crates/router/src/routes/locker_migration.rs @@ -14,7 +14,7 @@ pub async fn rust_locker_migration( ) -> HttpResponse { let flow = Flow::RustLockerMigration; let merchant_id = path.into_inner(); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -22,6 +22,6 @@ pub async fn rust_locker_migration( |state, _, _| locker_migration::rust_locker_migration(state, &merchant_id), &auth::AdminApiAuth, api_locking::LockAction::NotApplicable, - ) + )) .await } diff --git a/crates/router/src/routes/mandates.rs b/crates/router/src/routes/mandates.rs index 1e446136297..ecc89a10fa2 100644 --- a/crates/router/src/routes/mandates.rs +++ b/crates/router/src/routes/mandates.rs @@ -80,7 +80,9 @@ pub async fn revoke_mandate( state, &req, mandate_id, - |state, auth, req| mandate::revoke_mandate(state, auth.merchant_account, req), + |state, auth, req| { + mandate::revoke_mandate(state, auth.merchant_account, auth.key_store, req) + }, &auth::ApiKeyAuth, api_locking::LockAction::NotApplicable, ) diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 192df1a0929..b3629ab7d52 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -6,7 +6,9 @@ global_meter!(GLOBAL_METER, "ROUTER_API"); counter_metric!(HEALTH_METRIC, GLOBAL_METER); // No. of health API hits counter_metric!(KV_MISS, GLOBAL_METER); // No. of KV misses #[cfg(feature = "kms")] -counter_metric!(AWS_KMS_FAILURES, GLOBAL_METER); // No. of AWS KMS API failures +counter_metric!(AWS_KMS_ENCRYPTION_FAILURES, GLOBAL_METER); // No. of AWS KMS Encryption failures +#[cfg(feature = "kms")] +counter_metric!(AWS_KMS_DECRYPTION_FAILURES, GLOBAL_METER); // No. of AWS KMS Decryption failures // API Level Metrics counter_metric!(REQUESTS_RECEIVED, GLOBAL_METER); diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 43a7272a443..a6eeeabd687 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -108,7 +108,6 @@ pub async fn list_payment_method_api( get, path = "/customers/{customer_id}/payment_methods", params ( - ("customer_id" = String, Path, description = "The unique identifier for the customer account"), ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), @@ -134,10 +133,6 @@ pub async fn list_customer_payment_method_api( ) -> HttpResponse { let flow = Flow::CustomerPaymentMethodsList; let payload = query_payload.into_inner(); - let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { - Ok((auth, _auth_flow)) => (auth, _auth_flow), - Err(e) => return api::log_and_return_error_response(e), - }; let customer_id = customer_id.into_inner().0; Box::pin(api::server_wrap( flow, @@ -153,7 +148,7 @@ pub async fn list_customer_payment_method_api( Some(&customer_id), ) }, - &*auth, + &auth::ApiKeyAuth, api_locking::LockAction::NotApplicable, )) .await @@ -166,7 +161,6 @@ pub async fn list_customer_payment_method_api( path = "/customers/payment_methods", params ( ("client-secret" = String, Path, description = "A secret known only to your application and the authorization server"), - ("customer_id" = String, Path, description = "The unique identifier for the customer account"), ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index b836f02cded..34f41c49cdd 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -94,7 +94,7 @@ use crate::{ operation_id = "Create a Payment", security(("api_key" = [])), )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate, payment_id))] pub async fn payments_create( state: web::Data, req: actix_web::HttpRequest, @@ -111,6 +111,17 @@ pub async fn payments_create( return api::log_and_return_error_response(err); } + tracing::Span::current().record( + "payment_id", + &payload + .payment_id + .as_ref() + .map(|payment_id_type| payment_id_type.get_payment_intent_id()) + .transpose() + .unwrap_or_default() + .unwrap_or_default(), + ); + let locking_action = payload.get_locking_input(flow.clone()); Box::pin(api::server_wrap( @@ -159,7 +170,7 @@ pub async fn payments_create( // tag = "Payments", // operation_id = "Start a Redirection Payment" // )] -#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsStart))] +#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsStart, payment_id))] pub async fn payments_start( state: web::Data, req: actix_web::HttpRequest, @@ -174,6 +185,7 @@ pub async fn payments_start( }; let locking_action = payload.get_locking_input(flow.clone()); + tracing::Span::current().record("payment_id", &payment_id); Box::pin(api::server_wrap( flow, @@ -223,7 +235,7 @@ pub async fn payments_start( operation_id = "Retrieve a Payment", security(("api_key" = []), ("publishable_key" = [])) )] -#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsRetrieve))] +#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsRetrieve, payment_id))] // #[get("/{payment_id}")] pub async fn payments_retrieve( state: web::Data, @@ -241,6 +253,9 @@ pub async fn payments_retrieve( expand_captures: json_payload.expand_captures, ..Default::default() }; + + tracing::Span::current().record("payment_id", &path.to_string()); + let (auth_type, auth_flow) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { Ok(auth) => auth, @@ -291,7 +306,7 @@ pub async fn payments_retrieve( operation_id = "Retrieve a Payment", security(("api_key" = [])) )] -#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsRetrieve))] +#[instrument(skip(state, req), fields(flow = ?Flow::PaymentsRetrieve, payment_id))] // #[post("/sync")] pub async fn payments_retrieve_with_gateway_creds( state: web::Data, @@ -313,6 +328,8 @@ pub async fn payments_retrieve_with_gateway_creds( }; let flow = Flow::PaymentsRetrieve; + tracing::Span::current().record("payment_id", &json_payload.payment_id); + let locking_action = payload.get_locking_input(flow.clone()); Box::pin(api::server_wrap( @@ -356,7 +373,7 @@ pub async fn payments_retrieve_with_gateway_creds( operation_id = "Update a Payment", security(("api_key" = []), ("publishable_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdate))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdate, payment_id))] // #[post("/{payment_id}")] pub async fn payments_update( state: web::Data, @@ -373,6 +390,8 @@ pub async fn payments_update( let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = Some(payment_types::PaymentIdType::PaymentIntentId(payment_id)); let (auth_type, auth_flow) = match auth::get_auth_type_and_flow(req.headers()) { @@ -421,7 +440,7 @@ pub async fn payments_update( operation_id = "Confirm a Payment", security(("api_key" = []), ("publishable_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm, payment_id))] // #[post("/{payment_id}/confirm")] pub async fn payments_confirm( state: web::Data, @@ -441,6 +460,7 @@ pub async fn payments_confirm( } let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); payload.payment_id = Some(payment_types::PaymentIdType::PaymentIntentId(payment_id)); payload.confirm = Some(true); let header_payload = match payment_types::HeaderPayload::foreign_try_from(req.headers()) { @@ -497,7 +517,7 @@ pub async fn payments_confirm( operation_id = "Capture a Payment", security(("api_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsCapture))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCapture, payment_id))] // #[post("/{payment_id}/capture")] pub async fn payments_capture( state: web::Data, @@ -505,9 +525,12 @@ pub async fn payments_capture( json_payload: web::Json, path: web::Path, ) -> impl Responder { + let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + let flow = Flow::PaymentsCapture; let payload = payment_types::PaymentsCaptureRequest { - payment_id: path.into_inner(), + payment_id, ..json_payload.into_inner() }; @@ -558,7 +581,7 @@ pub async fn payments_capture( operation_id = "Create Session tokens for a Payment", security(("publishable_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsSessionToken))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsSessionToken, payment_id))] pub async fn payments_connector_session( state: web::Data, req: actix_web::HttpRequest, @@ -567,6 +590,8 @@ pub async fn payments_connector_session( let flow = Flow::PaymentsSessionToken; let payload = json_payload.into_inner(); + tracing::Span::current().record("payment_id", &payload.payment_id); + let locking_action = payload.get_locking_input(flow.clone()); Box::pin(api::server_wrap( @@ -617,7 +642,7 @@ pub async fn payments_connector_session( // tag = "Payments", // operation_id = "Get Redirect Response for a Payment" // )] -#[instrument(skip_all)] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsRedirect, payment_id))] pub async fn payments_redirect_response( state: web::Data, req: actix_web::HttpRequest, @@ -628,6 +653,8 @@ pub async fn payments_redirect_response( let (payment_id, merchant_id, connector) = path.into_inner(); let param_string = req.query_string(); + tracing::Span::current().record("payment_id", &payment_id); + let payload = payments::PaymentsRedirectResponseData { resource_id: payment_types::PaymentIdType::PaymentIntentId(payment_id), merchant_id: Some(merchant_id.clone()), @@ -676,7 +703,7 @@ pub async fn payments_redirect_response( // tag = "Payments", // operation_id = "Get Redirect Response for a Payment" // )] -#[instrument(skip_all)] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsRedirect, payment_id))] pub async fn payments_redirect_response_with_creds_identifier( state: web::Data, req: actix_web::HttpRequest, @@ -685,6 +712,8 @@ pub async fn payments_redirect_response_with_creds_identifier( let (payment_id, merchant_id, connector, creds_identifier) = path.into_inner(); let param_string = req.query_string(); + tracing::Span::current().record("payment_id", &payment_id); + let payload = payments::PaymentsRedirectResponseData { resource_id: payment_types::PaymentIdType::PaymentIntentId(payment_id), merchant_id: Some(merchant_id.clone()), @@ -715,7 +744,7 @@ pub async fn payments_redirect_response_with_creds_identifier( ) .await } -#[instrument(skip_all)] +#[instrument(skip_all, fields(flow =? Flow::PaymentsRedirect, payment_id))] pub async fn payments_complete_authorize( state: web::Data, req: actix_web::HttpRequest, @@ -726,6 +755,8 @@ pub async fn payments_complete_authorize( let (payment_id, merchant_id, connector) = path.into_inner(); let param_string = req.query_string(); + tracing::Span::current().record("payment_id", &payment_id); + let payload = payments::PaymentsRedirectResponseData { resource_id: payment_types::PaymentIdType::PaymentIntentId(payment_id), merchant_id: Some(merchant_id.clone()), @@ -774,7 +805,7 @@ pub async fn payments_complete_authorize( operation_id = "Cancel a Payment", security(("api_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsCancel))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCancel, payment_id))] // #[post("/{payment_id}/cancel")] pub async fn payments_cancel( state: web::Data, @@ -785,6 +816,9 @@ pub async fn payments_cancel( let flow = Flow::PaymentsCancel; let mut payload = json_payload.into_inner(); let payment_id = path.into_inner(); + + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; let locking_action = payload.get_locking_input(flow.clone()); Box::pin(api::server_wrap( @@ -919,7 +953,9 @@ pub async fn payments_approve( ) -> impl Responder { let mut payload = json_payload.into_inner(); let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; let flow = Flow::PaymentsApprove; let fpayload = FPaymentsApproveRequest(&payload); @@ -979,7 +1015,9 @@ pub async fn payments_reject( ) -> impl Responder { let mut payload = json_payload.into_inner(); let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; let flow = Flow::PaymentsReject; let fpayload = FPaymentsRejectRequest(&payload); @@ -1120,7 +1158,7 @@ where operation_id = "Increment authorized amount for a Payment", security(("api_key" = [])) )] -#[instrument(skip_all, fields(flow = ?Flow::PaymentsIncrementalAuthorization))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsIncrementalAuthorization, payment_id))] pub async fn payments_incremental_authorization( state: web::Data, req: actix_web::HttpRequest, @@ -1130,6 +1168,9 @@ pub async fn payments_incremental_authorization( let flow = Flow::PaymentsIncrementalAuthorization; let mut payload = json_payload.into_inner(); let payment_id = path.into_inner(); + + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; let locking_action = payload.get_locking_input(flow.clone()); Box::pin(api::server_wrap( @@ -1230,7 +1271,7 @@ impl GetLockingInput for payment_types::PaymentsRetrieveRequest { lock_utils::ApiIdentifier: From, { match self.resource_id { - payment_types::PaymentIdType::PaymentIntentId(ref id) => { + payment_types::PaymentIdType::PaymentIntentId(ref id) if self.force_sync => { api_locking::LockAction::Hold { input: api_locking::LockingInput { unique_locking_key: id.to_owned(), diff --git a/crates/router/src/routes/recon.rs b/crates/router/src/routes/recon.rs new file mode 100644 index 00000000000..d34e30237dd --- /dev/null +++ b/crates/router/src/routes/recon.rs @@ -0,0 +1,250 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::recon as recon_api; +use common_enums::ReconStatus; +use error_stack::ResultExt; +use masking::{ExposeInterface, PeekInterface, Secret}; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{ + api_locking, + errors::{self, RouterResponse, RouterResult, StorageErrorExt, UserErrors}, + }, + services::{ + api as service_api, api, + authentication::{self as auth, ReconUser, UserFromToken}, + email::types as email_types, + recon::ReconToken, + }, + types::{ + api::{self as api_types, enums}, + domain::{UserEmail, UserFromStorage, UserName}, + storage, + }, +}; + +pub async fn update_merchant( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ReconMerchantUpdate; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, _user, req| recon_merchant_account_update(state, req), + &auth::ReconAdmin, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn request_for_recon(state: web::Data, http_req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconServiceRequest; + Box::pin(api::server_wrap( + flow, + state, + &http_req, + (), + |state, user: UserFromToken, _req| send_recon_request(state, user), + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn get_recon_token(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconTokenRequest; + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, user: ReconUser, _| generate_recon_token(state, user), + &auth::ReconJWT, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn send_recon_request( + state: AppState, + user: UserFromToken, +) -> RouterResponse { + let db = &*state.store; + let user_from_db = db + .find_user_by_id(&user.user_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let merchant_id = db + .find_user_role_by_user_id(&user.user_id) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)? + .merchant_id; + let key_store = db + .get_merchant_key_store_by_merchant_id( + merchant_id.as_str(), + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + let merchant_account = db + .find_merchant_account_by_merchant_id(merchant_id.as_str(), &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let email_contents = email_types::ProFeatureRequest { + feature_name: "RECONCILIATION & SETTLEMENT".to_string(), + merchant_id: merchant_id.clone(), + user_name: UserName::new(user_from_db.name) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + recipient_email: UserEmail::from_pii_email(user_from_db.email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert to UserEmail from pii::Email")?, + settings: state.conf.clone(), + subject: format!( + "Dashboard Pro Feature Request by {}", + user_from_db.email.expose().peek() + ), + }; + + let is_email_sent = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to compose and send email for ProFeatureRequest") + .is_ok(); + + if is_email_sent { + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: enums::ReconStatus::Requested, + }; + + let response = db + .update_merchant(merchant_account, updated_merchant_account, &key_store) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id}") + })?; + + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconStatusResponse { + recon_status: response.recon_status, + }, + )) + } else { + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconStatusResponse { + recon_status: enums::ReconStatus::NotRequested, + }, + )) + } +} + +pub async fn recon_merchant_account_update( + state: AppState, + req: recon_api::ReconUpdateMerchantRequest, +) -> RouterResponse { + let merchant_id = &req.merchant_id.clone(); + let user_email = &req.user_email.clone(); + + let db = &*state.store; + + let key_store = db + .get_merchant_key_store_by_merchant_id( + &req.merchant_id, + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let merchant_account = db + .find_merchant_account_by_merchant_id(merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: req.recon_status, + }; + + let response = db + .update_merchant(merchant_account, updated_merchant_account, &key_store) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id}") + })?; + + let email_contents = email_types::ReconActivation { + recipient_email: UserEmail::from_pii_email(user_email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert to UserEmail from pii::Email")?, + user_name: UserName::new(Secret::new("HyperSwitch User".to_string())) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + settings: state.conf.clone(), + subject: "Approval of Recon Request - Access Granted to Recon Dashboard", + }; + + if req.recon_status == ReconStatus::Active { + let _is_email_sent = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to compose and send email for ReconActivation") + .is_ok(); + } + + Ok(service_api::ApplicationResponse::Json( + response + .try_into() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "merchant_account", + })?, + )) +} + +pub async fn generate_recon_token( + state: AppState, + req: ReconUser, +) -> RouterResponse { + let db = &*state.store; + let user = db + .find_user_by_id(&req.user_id) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(errors::ApiErrorResponse::InvalidJwtToken) + } else { + e.change_context(errors::ApiErrorResponse::InternalServerError) + } + })? + .into(); + + let token = Box::pin(get_recon_auth_token(user, state)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconTokenResponse { token }, + )) +} + +pub async fn get_recon_auth_token( + user: UserFromStorage, + state: AppState, +) -> RouterResult> { + ReconToken::new_token(user.0.user_id.clone(), &state.conf).await +} diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 594da67aa02..976fd5c9f56 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -33,7 +33,7 @@ pub async fn user_signup_with_merchant_id( &http_req, req_payload.clone(), |state, _, req_body| user_core::signup_with_merchant_id(state, req_body), - &auth::NoAuth, + &auth::AdminApiAuth, api_locking::LockAction::NotApplicable, )) .await @@ -333,7 +333,6 @@ pub async fn reset_password( .await } -#[cfg(feature = "email")] pub async fn invite_user( state: web::Data, req: HttpRequest, @@ -370,3 +369,37 @@ pub async fn verify_email( )) .await } + +#[cfg(feature = "email")] +pub async fn verify_email_request( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::VerifyEmailRequest; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _, req_body| user_core::send_verification_mail(state, req_body), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "recon")] +pub async fn verify_recon_token(state: web::Data, http_req: HttpRequest) -> HttpResponse { + let flow = Flow::ReconVerifyToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + (), + |state, user, _req| user_core::verify_token(state, user), + &auth::ReconJWT, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/webhooks.rs b/crates/router/src/routes/webhooks.rs index 2162ee56121..10eb4ef75e4 100644 --- a/crates/router/src/routes/webhooks.rs +++ b/crates/router/src/routes/webhooks.rs @@ -25,8 +25,8 @@ pub async fn receive_incoming_webhook( flow.clone(), state, &req, - (), - |state, auth, _| { + WebhookBytes(body), + |state, auth, payload| { webhooks::webhooks_wrapper::( &flow, state.to_owned(), @@ -34,7 +34,7 @@ pub async fn receive_incoming_webhook( auth.merchant_account, auth.key_store, &connector_id_or_name, - body.clone(), + payload.0, ) }, &auth::MerchantIdAuth(merchant_id), @@ -42,3 +42,19 @@ pub async fn receive_incoming_webhook( )) .await } + +#[derive(Debug)] +struct WebhookBytes(web::Bytes); + +impl serde::Serialize for WebhookBytes { + fn serialize(&self, serializer: S) -> Result { + let payload: serde_json::Value = serde_json::from_slice(&self.0).unwrap_or_default(); + payload.serialize(serializer) + } +} + +impl common_utils::events::ApiEventMetric for WebhookBytes { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Miscellaneous) + } +} diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 57f3b802bd5..8c973105d53 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -7,6 +7,8 @@ pub mod jwt; pub mod kafka; pub mod logger; pub mod pm_auth; +#[cfg(feature = "recon")] +pub mod recon; #[cfg(feature = "email")] pub mod email; diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 918aab929ac..ad463fcf2b9 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -12,13 +12,15 @@ use std::{ use actix_web::{body, web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError}; use api_models::enums::CaptureMethod; pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient}; +use common_enums::Currency; pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use common_utils::{ consts::X_HS_LATENCY, errors::{ErrorSwitch, ReportSwitchExt}, + request::RequestContent, }; use error_stack::{report, IntoReport, Report, ResultExt}; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::{PeekInterface, Secret}; use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag}; use serde::Serialize; use serde_json::json; @@ -34,7 +36,10 @@ use crate::{ errors::{self, CustomResult}, payments, }, - events::api_logs::{ApiEvent, ApiEventMetric, ApiEventsType}, + events::{ + api_logs::{ApiEvent, ApiEventMetric, ApiEventsType}, + connector_api_logs::ConnectorEvent, + }, logger, routes::{ app::AppStateInfo, @@ -96,10 +101,6 @@ pub trait ConnectorValidation: ConnectorCommon { fn is_webhook_source_verification_mandatory(&self) -> bool { false } - - fn validate_if_surcharge_implemented(&self) -> CustomResult<(), errors::ConnectorError> { - Err(errors::ConnectorError::NotImplemented(format!("Surcharge for {}", self.id())).into()) - } } #[async_trait::async_trait] @@ -133,8 +134,8 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(None) + ) -> CustomResult { + Ok(RequestContent::Json(Box::new(json!(r#"{}"#)))) } fn get_request_form_data( @@ -278,6 +279,7 @@ where { // If needed add an error stack as follows // connector_integration.build_request(req).attach_printable("Failed to build request"); + logger::debug!(connector_request=?connector_request); let mut router_data = req.clone(); match call_connector_action { payments::CallConnectorAction::HandleResponse(res) => { @@ -351,10 +353,58 @@ where match connector_request { Some(request) => { logger::debug!(connector_request=?request); + + let masked_request_body = match &request.body { + Some(request) => match request { + RequestContent::Json(i) + | RequestContent::FormUrlEncoded(i) + | RequestContent::Xml(i) => i + .masked_serialize() + .unwrap_or(json!({ "error": "failed to mask serialize"})), + RequestContent::FormData(_) => json!({"request_type": "FORM_DATA"}), + }, + None => serde_json::Value::Null, + }; + let request_url = request.url.clone(); + let request_method = request.method; + let current_time = Instant::now(); let response = call_connector_api(state, request).await; let external_latency = current_time.elapsed().as_millis(); logger::debug!(connector_response=?response); + + let connector_event = ConnectorEvent::new( + req.connector.clone(), + std::any::type_name::(), + masked_request_body, + response + .as_ref() + .map(|response| { + response + .as_ref() + .map_or_else(|value| value, |value| value) + .response + .escape_ascii() + .to_string() + }) + .ok(), + request_url, + request_method, + req.payment_id.clone(), + req.merchant_id.clone(), + state.request_id.as_ref(), + external_latency, + ); + + match connector_event.try_into() { + Ok(event) => { + state.event_handler().log_event(event); + } + Err(err) => { + logger::error!(error=?err, "Error Logging Connector Event"); + } + } + match response { Ok(body) => { let response = match body { @@ -477,7 +527,7 @@ pub async fn send_request( request: Request, option_timeout_secs: Option, ) -> CustomResult { - logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request); + logger::debug!(method=?request.method, headers=?request.headers, payload=?request.body, ?request); let url = reqwest::Url::parse(&request.url) .into_report() @@ -508,46 +558,49 @@ pub async fn send_request( Method::Get => client.get(url), Method::Post => { let client = client.post(url); - match request.content_type { - Some(ContentType::Json) => client.json(&request.payload), - - Some(ContentType::FormData) => { - client.multipart(request.form_data.unwrap_or_default()) + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") } - - // Currently this is not used remove this if not required - // If using this then handle the serde_part - Some(ContentType::FormUrlEncoded) => { - let payload = match request.payload.clone() { - Some(req) => serde_json::from_str(req.peek()) - .into_report() - .change_context(errors::ApiClientError::UrlEncodingFailed)?, - _ => json!(r#""#), - }; - let url_encoded_payload = serde_urlencoded::to_string(&payload) + None => client, + } + } + Method::Put => { + let client = client.put(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) .into_report() - .change_context(errors::ApiClientError::UrlEncodingFailed) - .attach_printable_lazy(|| { - format!( - "Unable to do url encoding on request: {:?}", - &request.payload - ) - })?; - - logger::debug!(?url_encoded_payload); - client.body(url_encoded_payload) + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") } - // If payload needs processing the body cannot have default - None => client.body(request.payload.expose_option().unwrap_or_default()), + None => client, + } + } + Method::Patch => { + let client = client.patch(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") + } + None => client, } } - - Method::Put => client - .put(url) - .body(request.payload.expose_option().unwrap_or_default()), // If payload needs processing the body cannot have default - Method::Patch => client - .patch(url) - .body(request.payload.expose_option().unwrap_or_default()), Method::Delete => client.delete(url), } .add_headers(headers) @@ -680,11 +733,17 @@ pub enum ApplicationResponse { TextPlain(String), JsonForRedirection(api::RedirectionResponse), Form(Box), - PaymenkLinkForm(Box), + PaymenkLinkForm(Box), FileData((Vec, mime::Mime)), JsonWithHeaders((R, Vec<(String, String)>)), } +#[derive(Debug, Eq, PartialEq)] +pub enum PaymentLinkAction { + PaymentLinkFormData(PaymentLinkFormData), + PaymentLinkStatus(PaymentLinkStatusData), +} + #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentLinkFormData { pub js_script: String, @@ -692,6 +751,12 @@ pub struct PaymentLinkFormData { pub sdk_url: String, } +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentLinkStatusData { + pub js_script: String, + pub css_script: String, +} + #[derive(Debug, Eq, PartialEq)] pub struct RedirectionFormData { pub redirect_form: RedirectForm, @@ -724,12 +789,28 @@ pub enum RedirectForm { BlueSnap { payment_fields_token: String, // payment-field-token }, + CybersourceAuthSetup { + access_token: String, + ddc_url: String, + reference_id: String, + }, + CybersourceConsumerAuth { + access_token: String, + step_up_url: String, + }, Payme, Braintree { client_token: String, card_token: String, bin: String, }, + Nmi { + amount: String, + currency: Currency, + public_key: Secret, + customer_vault_id: String, + order_id: String, + }, } impl From<(url::Url, Method)> for RedirectForm { @@ -888,7 +969,7 @@ where error, event_type.unwrap_or(ApiEventsType::Miscellaneous), request, - Some(request.method().to_string()), + request.method(), ); match api_event.clone().try_into() { Ok(event) => { @@ -991,16 +1072,32 @@ where .map_into_boxed_body() } - Ok(ApplicationResponse::PaymenkLinkForm(payment_link_data)) => { - match build_payment_link_html(*payment_link_data) { - Ok(rendered_html) => http_response_html_data(rendered_html), - Err(_) => http_response_err( - r#"{ - "error": { - "message": "Error while rendering payment link html page" - } - }"#, - ), + Ok(ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => { + match *boxed_payment_link_data { + PaymentLinkAction::PaymentLinkFormData(payment_link_data) => { + match build_payment_link_html(payment_link_data) { + Ok(rendered_html) => http_response_html_data(rendered_html), + Err(_) => http_response_err( + r#"{ + "error": { + "message": "Error while rendering payment link html page" + } + }"#, + ), + } + } + PaymentLinkAction::PaymentLinkStatus(payment_link_data) => { + match get_payment_link_status(payment_link_data) { + Ok(rendered_html) => http_response_html_data(rendered_html), + Err(_) => http_response_err( + r#"{ + "error": { + "message": "Error while rendering payment link status page" + } + }"#, + ), + } + } } } @@ -1088,6 +1185,14 @@ pub fn http_response_json(response: T) -> HttpRe .body(response) } +pub fn http_server_error_json_response( + response: T, +) -> HttpResponse { + HttpResponse::InternalServerError() + .content_type(mime::APPLICATION_JSON) + .body(response) +} + pub fn http_response_json_with_headers( response: T, mut headers: Vec<(String, String)>, @@ -1330,6 +1435,105 @@ pub fn build_redirection_form( "))) }} } + RedirectForm::CybersourceAuthSetup { + access_token, + ddc_url, + reference_id, + } => { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta name="viewport" content="width=device-width, initial-scale=1"; + } + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" } + + (PreEscaped(r#""#)) + + (PreEscaped(r#" + + "#)) + + + h3 style="text-align: center;" { "Please wait while we process your payment..." } + } + + (PreEscaped(r#""#)) + (PreEscaped(format!("
+ +
"))) + (PreEscaped(r#""#)) + (PreEscaped(format!(" + "))) + }} + } + RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + } => { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta name="viewport" content="width=device-width, initial-scale=1"; + } + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" } + + (PreEscaped(r#""#)) + + (PreEscaped(r#" + + "#)) + + + h3 style="text-align: center;" { "Please wait while we process your payment..." } + } + + // This is the iframe recommended by cybersource but the redirection happens inside this iframe once otp + // is received and we lose control of the redirection on user client browser, so to avoid that we have removed this iframe and directly consumed it. + // (PreEscaped(r#""#)) + (PreEscaped(format!("
+ +
"))) + (PreEscaped(r#""#)) + }} + } RedirectForm::Payme => { maud::html! { (maud::DOCTYPE) @@ -1453,14 +1657,86 @@ pub fn build_redirection_form( ))) }} } - } -} + RedirectForm::Nmi { + amount, + currency, + public_key, + customer_vault_id, + order_id, + } => { + let public_key_val = public_key.peek(); + maud::html! { + (maud::DOCTYPE) + head { + (PreEscaped(r#""#)) + } + (PreEscaped(format!("" + ))) + } + } } } @@ -1491,5 +1767,33 @@ pub fn build_payment_link_html( } fn get_hyper_loader_sdk(sdk_url: &str) -> String { - format!("") + format!("") +} + +pub fn get_payment_link_status( + payment_link_data: PaymentLinkStatusData, +) -> CustomResult { + let html_template = include_str!("../core/payment_link/status.html").to_string(); + let mut tera = Tera::default(); + let _ = tera.add_raw_template("payment_link_status", &html_template); + + let mut context = Context::new(); + context.insert("css_color_scheme", &payment_link_data.css_script); + context.insert("payment_details_js_script", &payment_link_data.js_script); + + match tera.render("payment_link_status", &context) { + Ok(rendered_html) => Ok(rendered_html), + Err(tera_error) => { + crate::logger::warn!("{tera_error}"); + Err(errors::ApiErrorResponse::InternalServerError)? + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_mime_essence() { + assert_eq!(mime::APPLICATION_JSON.essence_str(), "application/json"); + } } diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs index cc7353dcda6..f4d74c4f81b 100644 --- a/crates/router/src/services/api/client.rs +++ b/crates/router/src/services/api/client.rs @@ -10,6 +10,7 @@ use router_env::tracing_actix_web::RequestId; use super::{request::Maskable, Request}; use crate::{ configs::settings::{Locker, Proxy}, + consts::LOCKER_HEALTH_CALL_PATH, core::{ errors::{ApiClientError, CustomResult}, payments, @@ -111,7 +112,6 @@ pub(super) fn create_client( pub fn proxy_bypass_urls(locker: &Locker) -> Vec { let locker_host = locker.host.to_owned(); let locker_host_rs = locker.host_rs.to_owned(); - let basilisk_host = locker.basilisk_host.to_owned(); vec![ format!("{locker_host}/cards/add"), format!("{locker_host}/cards/retrieve"), @@ -119,13 +119,10 @@ pub fn proxy_bypass_urls(locker: &Locker) -> Vec { format!("{locker_host_rs}/cards/add"), format!("{locker_host_rs}/cards/retrieve"), format!("{locker_host_rs}/cards/delete"), + format!("{locker_host_rs}{}", LOCKER_HEALTH_CALL_PATH), format!("{locker_host}/card/addCard"), format!("{locker_host}/card/getCard"), format!("{locker_host}/card/deleteCard"), - format!("{basilisk_host}/tokenize"), - format!("{basilisk_host}/tokenize/get"), - format!("{basilisk_host}/tokenize/delete"), - format!("{basilisk_host}/tokenize/delete/token"), ] } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index b48465ebd17..3370912394e 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -12,10 +12,14 @@ use serde::Serialize; use super::authorization::{self, permissions::Permission}; #[cfg(feature = "olap")] use super::jwt; +#[cfg(feature = "recon")] +use super::recon::ReconToken; #[cfg(feature = "olap")] use crate::consts; #[cfg(feature = "olap")] use crate::core::errors::UserResult; +#[cfg(feature = "recon")] +use crate::routes::AppState; use crate::{ configs::settings, core::{ @@ -822,3 +826,95 @@ where } default_auth } + +#[cfg(feature = "recon")] +static RECON_API_KEY: tokio::sync::OnceCell> = + tokio::sync::OnceCell::const_new(); + +#[cfg(feature = "recon")] +pub async fn get_recon_admin_api_key( + secrets: &settings::Secrets, + #[cfg(feature = "kms")] kms_client: &kms::KmsClient, +) -> RouterResult<&'static StrongSecret> { + RECON_API_KEY + .get_or_try_init(|| async { + #[cfg(feature = "kms")] + let recon_admin_api_key = secrets + .kms_encrypted_recon_admin_api_key + .decrypt_inner(kms_client) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to KMS decrypt recon admin API key")?; + + #[cfg(not(feature = "kms"))] + let recon_admin_api_key = secrets.recon_admin_api_key.clone(); + + Ok(StrongSecret::new(recon_admin_api_key)) + }) + .await +} + +#[cfg(feature = "recon")] +pub struct ReconAdmin; + +#[async_trait] +#[cfg(feature = "recon")] +impl AuthenticateAndFetch<(), A> for ReconAdmin +where + A: AppStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let request_admin_api_key = + get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; + let conf = state.conf(); + + let admin_api_key = get_recon_admin_api_key( + &conf.secrets, + #[cfg(feature = "kms")] + kms::get_kms_client(&conf.kms).await, + ) + .await?; + + if request_admin_api_key != admin_api_key.peek() { + Err(report!(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Recon Admin Authentication Failure"))?; + } + + Ok(((), AuthenticationType::NoAuth)) + } +} + +#[cfg(feature = "recon")] +pub struct ReconJWT; +#[cfg(feature = "recon")] +pub struct ReconUser { + pub user_id: String, +} +#[cfg(feature = "recon")] +impl AuthInfo for ReconUser { + fn get_merchant_id(&self) -> Option<&str> { + None + } +} +#[cfg(all(feature = "olap", feature = "recon"))] +#[async_trait] +impl AuthenticateAndFetch for ReconJWT { + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &AppState, + ) -> RouterResult<(ReconUser, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + + Ok(( + ReconUser { + user_id: payload.user_id, + }, + AuthenticationType::NoAuth, + )) + } +} diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index c6b649f3de5..cef93f82739 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -38,6 +38,7 @@ pub enum PermissionModule { Routing, Analytics, Mandates, + Customer, Disputes, Files, ThreeDsDecisionManager, @@ -55,6 +56,7 @@ impl PermissionModule { 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::Customer => "Everything related to customers - like creating and viewing customer related information are within this module", Self::Disputes => "Everything related to disputes - like creating and viewing dispute related information are within this module", Self::Files => "Permissions for uploading, deleting and viewing files for disputes", Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", @@ -133,6 +135,14 @@ impl ModuleInfo { Permission::MandateWrite, ]), }, + PermissionModule::Customer => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::CustomerRead, + Permission::CustomerWrite, + ]), + }, PermissionModule::Disputes => Self { module: module_name, description, diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 708da97e1e3..426b048e88b 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -19,6 +19,8 @@ pub enum Permission { DisputeWrite, MandateRead, MandateWrite, + CustomerRead, + CustomerWrite, FileRead, FileWrite, Analytics, @@ -55,6 +57,8 @@ impl Permission { Self::DisputeWrite => Some("Create and update disputes"), Self::MandateRead => Some("View mandates"), Self::MandateWrite => Some("Create and update mandates"), + Self::CustomerRead => Some("View customers"), + Self::CustomerWrite => Some("Create, update and delete customers"), Self::FileRead => Some("View files"), Self::FileWrite => Some("Create, update and delete files"), Self::Analytics => Some("Access to analytics module"), diff --git a/crates/router/src/services/authorization/predefined_permissions.rs b/crates/router/src/services/authorization/predefined_permissions.rs index a9f2b864d0a..c489f1fc963 100644 --- a/crates/router/src/services/authorization/predefined_permissions.rs +++ b/crates/router/src/services/authorization/predefined_permissions.rs @@ -52,6 +52,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -79,6 +81,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::Analytics, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::UsersRead, ], @@ -112,6 +115,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -150,6 +155,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -175,6 +182,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -198,6 +206,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -223,6 +232,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -252,6 +262,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerWrite, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -273,6 +284,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::MerchantAccountRead, Permission::MerchantConnectorAccountRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::FileWrite, Permission::Analytics, diff --git a/crates/router/src/services/email/assets/recon_activated.html b/crates/router/src/services/email/assets/recon_activation.html similarity index 100% rename from crates/router/src/services/email/assets/recon_activated.html rename to crates/router/src/services/email/assets/recon_activation.html diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 9e26c45ba6b..0ef15eaa40d 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -1,17 +1,37 @@ use common_utils::errors::CustomResult; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; -use masking::ExposeInterface; +use masking::{ExposeInterface, PeekInterface}; use crate::{configs, consts}; #[cfg(feature = "olap")] use crate::{core::errors::UserErrors, services::jwt, types::domain}; pub enum EmailBody { - Verify { link: String }, - Reset { link: String, user_name: String }, - MagicLink { link: String, user_name: String }, - InviteUser { link: String, user_name: String }, + Verify { + link: String, + }, + Reset { + link: String, + user_name: String, + }, + MagicLink { + link: String, + user_name: String, + }, + InviteUser { + link: String, + user_name: String, + }, + ReconActivation { + user_name: String, + }, + ProFeatureRequest { + feature_name: String, + merchant_id: String, + user_name: String, + user_email: String, + }, } pub mod html { @@ -43,6 +63,30 @@ pub mod html { link = link ) } + EmailBody::ReconActivation { user_name } => { + format!( + include_str!("assets/recon_activation.html"), + username = user_name, + ) + } + EmailBody::ProFeatureRequest { + feature_name, + merchant_id, + user_name, + user_email, + } => { + format!( + "Dear Hyperswitch Support Team, + + Dashboard Pro Feature Request, + Feature name : {feature_name} + Merchant ID : {merchant_id} + Merchant Name : {user_name} + Email : {user_email} + + (note: This is an auto generated email. use merchant email for any further comunications)", + ) + } } } } @@ -77,7 +121,7 @@ pub fn get_link_with_token( token: impl std::fmt::Display, action: impl std::fmt::Display, ) -> String { - format!("{base_url}/user/{action}/?token={token}") + format!("{base_url}/user/{action}?token={token}") } pub struct VerifyEmail { @@ -153,7 +197,8 @@ impl EmailData for MagicLink { .await .change_context(EmailError::TokenGenerationFailure)?; - let magic_link_login = get_link_with_token(&self.settings.email.base_url, token, "login"); + let magic_link_login = + get_link_with_token(&self.settings.email.base_url, token, "verify_email"); let body = html::get_html_body(EmailBody::MagicLink { link: magic_link_login, @@ -197,3 +242,54 @@ impl EmailData for InviteUser { }) } } + +pub struct ReconActivation { + pub recipient_email: domain::UserEmail, + pub user_name: domain::UserName, + pub settings: std::sync::Arc, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for ReconActivation { + async fn get_email_data(&self) -> CustomResult { + let body = html::get_html_body(EmailBody::ReconActivation { + user_name: self.user_name.clone().get_secret().expose(), + }); + + Ok(EmailContents { + subject: self.subject.to_string(), + body: external_services::email::IntermediateString::new(body), + recipient: self.recipient_email.clone().into_inner(), + }) + } +} + +pub struct ProFeatureRequest { + pub recipient_email: domain::UserEmail, + pub feature_name: String, + pub merchant_id: String, + pub user_name: domain::UserName, + pub settings: std::sync::Arc, + pub subject: String, +} + +#[async_trait::async_trait] +impl EmailData for ProFeatureRequest { + async fn get_email_data(&self) -> CustomResult { + let recipient = self.recipient_email.clone().into_inner(); + + let body = html::get_html_body(EmailBody::ProFeatureRequest { + user_name: self.user_name.clone().get_secret().expose(), + feature_name: self.feature_name.clone(), + merchant_id: self.merchant_id.clone(), + user_email: recipient.peek().to_string(), + }); + + Ok(EmailContents { + subject: self.subject.clone(), + body: external_services::email::IntermediateString::new(body), + recipient, + }) + } +} diff --git a/crates/router/src/services/encryption.rs b/crates/router/src/services/encryption.rs index acea96f8660..9a8a5f4af4a 100644 --- a/crates/router/src/services/encryption.rs +++ b/crates/router/src/services/encryption.rs @@ -1,9 +1,7 @@ -use std::{num::Wrapping, str}; +use std::str; use error_stack::{report, IntoReport, ResultExt}; use josekit::{jwe, jws}; -use rand; -use ring::{aead::*, error::Unspecified}; use serde::{Deserialize, Serialize}; use crate::{ @@ -11,44 +9,6 @@ use crate::{ utils, }; -struct NonceGen { - current: Wrapping, - start: u128, -} - -impl NonceGen { - fn new(start: [u8; 12]) -> Self { - let mut array = [0; 16]; - array[..12].copy_from_slice(&start); - let start = if cfg!(target_endian = "little") { - u128::from_le_bytes(array) - } else { - u128::from_be_bytes(array) - }; - Self { - current: Wrapping(start), - start, - } - } -} - -impl NonceSequence for NonceGen { - fn advance(&mut self) -> Result { - let n = self.current.0; - self.current += 1; - if self.current.0 == self.start { - return Err(Unspecified); - } - let value = if cfg!(target_endian = "little") { - n.to_le_bytes()[..12].try_into()? - } else { - n.to_be_bytes()[..12].try_into()? - }; - let nonce = Nonce::assume_unique_for_key(value); - Ok(nonce) - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JwsBody { pub header: String, @@ -66,57 +26,6 @@ pub struct JweBody { pub encrypted_key: String, } -pub fn encrypt(msg: &String, key: &[u8]) -> CustomResult, errors::EncryptionError> { - let nonce_seed = rand::random(); - let mut sealing_key = { - let key = UnboundKey::new(&AES_256_GCM, key) - .map_err(errors::EncryptionError::from) - .into_report() - .attach_printable("Unbound Key Error")?; - let nonce_gen = NonceGen::new(nonce_seed); - SealingKey::new(key, nonce_gen) - }; - let msg_byte = msg.as_bytes(); - let mut data = msg_byte.to_vec(); - - sealing_key - .seal_in_place_append_tag(Aad::empty(), &mut data) - .map_err(errors::EncryptionError::from) - .into_report() - .attach_printable("Error Encrypting")?; - let nonce_vec = nonce_seed.to_vec(); - data.splice(0..0, nonce_vec); //prepend nonce at the start - Ok(data) -} - -pub fn decrypt(mut data: Vec, key: &[u8]) -> CustomResult { - let nonce_seed = data[0..12] - .try_into() - .into_report() - .change_context(errors::EncryptionError) - .attach_printable("Error getting nonce")?; - data.drain(0..12); - - let mut opening_key = { - let key = UnboundKey::new(&AES_256_GCM, key) - .map_err(errors::EncryptionError::from) - .into_report() - .attach_printable("Unbound Key Error")?; - let nonce_gen = NonceGen::new(nonce_seed); - OpeningKey::new(key, nonce_gen) - }; - let res_byte = opening_key - .open_in_place(Aad::empty(), &mut data) - .map_err(errors::EncryptionError::from) - .into_report() - .attach_printable("Error Decrypting")?; - let response = str::from_utf8_mut(res_byte) - .into_report() - .change_context(errors::EncryptionError) - .attach_printable("Error from_utf8")?; - Ok(response.to_string()) -} - pub async fn encrypt_jwe( payload: &[u8], public_key: impl AsRef<[u8]>, @@ -220,7 +129,6 @@ mod tests { #![allow(clippy::unwrap_used, clippy::expect_used)] use super::*; - use crate::utils::{self, ValueExt}; // Keys used for tests // Can be generated using the following commands: @@ -308,21 +216,6 @@ VuY3OeNxi+dC2r7HppP3O/MJ4gX/RJJfSrcaGP8/Ke1W5+jE97Qy -----END RSA PRIVATE KEY----- "; - fn generate_key() -> [u8; 32] { - let key: [u8; 32] = rand::random(); - key - } - - #[test] - fn test_enc() { - let key = generate_key(); - let enc_data = encrypt(&"Test_Encrypt".to_string(), &key).unwrap(); - let card_info = utils::Encode::>::encode_to_value(&enc_data).unwrap(); - let data: Vec = card_info.parse_value("ParseEncryptedData").unwrap(); - let dec_data = decrypt(data, &key).unwrap(); - assert_eq!(dec_data, "Test_Encrypt".to_string()); - } - #[actix_rt::test] async fn test_jwe() { let jwt = encrypt_jwe("request_payload".as_bytes(), ENCRYPTION_KEY) diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 497ac16721b..2bcfcfe974f 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -8,12 +8,9 @@ use rdkafka::{ }; use crate::events::EventType; -mod api_event; -pub mod outgoing_request; mod payment_attempt; mod payment_intent; mod refund; -pub use api_event::{ApiCallEventType, ApiEvents, ApiEventsType}; use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use diesel_models::refund::Refund; use serde::Serialize; @@ -83,6 +80,8 @@ pub struct KafkaSettings { attempt_analytics_topic: String, refund_analytics_topic: String, api_logs_topic: String, + connector_logs_topic: String, + outgoing_webhook_logs_topic: String, } impl KafkaSettings { @@ -119,7 +118,24 @@ impl KafkaSettings { Err(ApplicationError::InvalidConfigurationValueError( "Kafka API event Analytics topic must not be empty".into(), )) - }) + })?; + + common_utils::fp_utils::when(self.connector_logs_topic.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Kafka Connector Logs topic must not be empty".into(), + )) + })?; + + common_utils::fp_utils::when( + self.outgoing_webhook_logs_topic.is_default_or_empty(), + || { + Err(ApplicationError::InvalidConfigurationValueError( + "Kafka Outgoing Webhook Logs topic must not be empty".into(), + )) + }, + )?; + + Ok(()) } } @@ -130,6 +146,8 @@ pub struct KafkaProducer { attempt_analytics_topic: String, refund_analytics_topic: String, api_logs_topic: String, + connector_logs_topic: String, + outgoing_webhook_logs_topic: String, } struct RdKafkaProducer(ThreadedProducer); @@ -166,6 +184,8 @@ impl KafkaProducer { attempt_analytics_topic: conf.attempt_analytics_topic.clone(), refund_analytics_topic: conf.refund_analytics_topic.clone(), api_logs_topic: conf.api_logs_topic.clone(), + connector_logs_topic: conf.connector_logs_topic.clone(), + outgoing_webhook_logs_topic: conf.outgoing_webhook_logs_topic.clone(), }) } @@ -286,17 +306,14 @@ impl KafkaProducer { }) } - pub async fn log_api_event(&self, event: &ApiEvents) -> MQResult<()> { - self.log_kafka_event(&self.api_logs_topic, event) - .attach_printable_lazy(|| format!("Failed to add api log event {event:?}")) - } - pub fn get_topic(&self, event: EventType) -> &str { match event { EventType::ApiLogs => &self.api_logs_topic, EventType::PaymentAttempt => &self.attempt_analytics_topic, EventType::PaymentIntent => &self.intent_analytics_topic, EventType::Refund => &self.refund_analytics_topic, + EventType::ConnectorApiLogs => &self.connector_logs_topic, + EventType::OutgoingWebhookLogs => &self.outgoing_webhook_logs_topic, } } } diff --git a/crates/router/src/services/kafka/api_event.rs b/crates/router/src/services/kafka/api_event.rs deleted file mode 100644 index 7de27191592..00000000000 --- a/crates/router/src/services/kafka/api_event.rs +++ /dev/null @@ -1,108 +0,0 @@ -use api_models::enums as api_enums; -use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(tag = "flow_type")] -pub enum ApiEventsType { - Payment { - payment_id: String, - }, - Refund { - payment_id: String, - refund_id: String, - }, - Default, - PaymentMethod { - payment_method_id: String, - payment_method: Option, - payment_method_type: Option, - }, - Customer { - customer_id: String, - }, - User { - //specified merchant_id will overridden on global defined - merchant_id: String, - user_id: String, - }, - Webhooks { - connector: String, - payment_id: Option, - }, - OutgoingEvent, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct ApiEvents { - pub api_name: String, - pub request_id: Option, - //It is require to solve ambiquity in case of event_type is User - #[serde(skip_serializing_if = "Option::is_none")] - pub merchant_id: Option, - pub request: String, - pub response: String, - pub status_code: u16, - #[serde(with = "time::serde::timestamp")] - pub created_at: OffsetDateTime, - pub latency: u128, - //conflicting fields underlying enums will be used - #[serde(flatten)] - pub event_type: ApiEventsType, - pub user_agent: Option, - pub ip_addr: Option, - pub url_path: Option, - pub api_event_type: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum ApiCallEventType { - IncomingApiEvent, - OutgoingApiEvent, -} - -impl super::KafkaMessage for ApiEvents { - fn key(&self) -> String { - match &self.event_type { - ApiEventsType::Payment { payment_id } => format!( - "{}_{}", - self.merchant_id - .as_ref() - .unwrap_or(&"default_merchant_id".to_string()), - payment_id - ), - ApiEventsType::Refund { - payment_id, - refund_id, - } => format!("{payment_id}_{refund_id}"), - ApiEventsType::Default => "key".to_string(), - ApiEventsType::PaymentMethod { - payment_method_id, - payment_method, - payment_method_type, - } => format!( - "{:?}_{:?}_{:?}", - payment_method_id.clone(), - payment_method.clone(), - payment_method_type.clone(), - ), - ApiEventsType::Customer { customer_id } => customer_id.to_string(), - ApiEventsType::User { - merchant_id, - user_id, - } => format!("{}_{}", merchant_id, user_id), - ApiEventsType::Webhooks { - connector, - payment_id, - } => format!( - "webhook_{}_{connector}", - payment_id.clone().unwrap_or_default() - ), - ApiEventsType::OutgoingEvent => "outgoing_event".to_string(), - } - } - - fn creation_timestamp(&self) -> Option { - Some(self.created_at.unix_timestamp()) - } -} diff --git a/crates/router/src/services/kafka/outgoing_request.rs b/crates/router/src/services/kafka/outgoing_request.rs deleted file mode 100644 index bb09fe91fe6..00000000000 --- a/crates/router/src/services/kafka/outgoing_request.rs +++ /dev/null @@ -1,19 +0,0 @@ -use reqwest::Url; - -pub struct OutgoingRequest { - pub url: Url, - pub latency: u128, -} - -// impl super::KafkaMessage for OutgoingRequest { -// fn key(&self) -> String { -// format!( -// "{}_{}", - -// ) -// } - -// fn creation_timestamp(&self) -> Option { -// Some(self.created_at.unix_timestamp()) -// } -// } diff --git a/crates/router/src/services/recon.rs b/crates/router/src/services/recon.rs new file mode 100644 index 00000000000..d5a2151a487 --- /dev/null +++ b/crates/router/src/services/recon.rs @@ -0,0 +1,29 @@ +use error_stack::ResultExt; +use masking::Secret; + +use super::jwt; +use crate::{ + consts, + core::{self, errors::RouterResult}, + routes::app::settings::Settings, +}; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ReconToken { + pub user_id: String, + pub exp: u64, +} + +impl ReconToken { + pub async fn new_token(user_id: String, settings: &Settings) -> RouterResult> { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration) + .change_context(core::errors::ApiErrorResponse::InternalServerError)? + .as_secs(); + let token_payload = Self { user_id, exp }; + let token = jwt::generate_jwt(&token_payload, settings) + .await + .change_context(core::errors::ApiErrorResponse::InternalServerError)?; + Ok(Secret::new(token)) + } +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index aa563c647ea..e236113e676 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -19,9 +19,10 @@ use std::{collections::HashMap, marker::PhantomData}; pub use api_models::{ enums::{Connector, PayoutConnectors}, - payouts as payout_types, + mandates, payouts as payout_types, }; -pub use common_utils::request::RequestBody; +use common_enums::MandateStatus; +pub use common_utils::request::{RequestBody, RequestContent}; use common_utils::{pii, pii::Email}; use data_models::mandates::MandateData; use error_stack::{IntoReport, ResultExt}; @@ -38,7 +39,7 @@ use crate::{ payments::{types, PaymentData, RecurringMandatePaymentData}, }, services, - types::{storage::payment_attempt::PaymentAttemptExt, transformers::ForeignFrom}, + types::transformers::ForeignFrom, utils::OptionExt, }; @@ -117,6 +118,11 @@ pub type SetupMandateType = dyn services::ConnectorIntegration< SetupMandateRequestData, PaymentsResponseData, >; +pub type MandateRevokeType = dyn services::ConnectorIntegration< + api::MandateRevoke, + MandateRevokeRequestData, + MandateRevokeResponseData, +>; pub type PaymentsPreProcessingType = dyn services::ConnectorIntegration< api::PreProcessing, PaymentsPreProcessingData, @@ -246,6 +252,9 @@ pub type RetrieveFileRouterData = pub type DefendDisputeRouterData = RouterData; +pub type MandateRevokeRouterData = + RouterData; + #[cfg(feature = "payouts")] pub type PayoutsRouterData = RouterData; @@ -308,6 +317,8 @@ pub struct RouterData { pub external_latency: Option, /// Contains apple pay flow type simplified or manual pub apple_pay_flow: Option, + + pub frm_metadata: Option, } #[derive(Debug, Clone, serde::Deserialize)] @@ -371,6 +382,14 @@ pub struct PayoutsFulfillResponseData { #[derive(Debug, Clone)] pub struct PaymentsAuthorizeData { pub payment_method_data: payments::PaymentMethodData, + /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount) + /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately + /// ``` + /// get_original_amount() + /// get_surcharge_amount() + /// get_tax_on_surcharge_amount() + /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount + /// ``` pub amount: i64, pub email: Option, pub currency: storage_enums::Currency, @@ -397,6 +416,7 @@ pub struct PaymentsAuthorizeData { pub surcharge_details: Option, pub customer_id: Option, pub request_incremental_authorization: bool, + pub metadata: Option, } #[derive(Debug, Clone, Default)] @@ -408,6 +428,8 @@ pub struct PaymentsCaptureData { pub multiple_capture_data: Option, pub connector_meta: Option, pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. } #[derive(Debug, Clone, Default)] @@ -468,6 +490,7 @@ pub struct PaymentsPreProcessingData { pub surcharge_details: Option, pub browser_info: Option, pub connector_transaction_id: Option, + pub redirect_response: Option, } #[derive(Debug, Clone)] @@ -488,6 +511,8 @@ pub struct CompleteAuthorizeData { pub browser_info: Option, pub connector_transaction_id: Option, pub connector_meta: Option, + pub complete_authorize_url: Option, + pub metadata: Option, } #[derive(Debug, Clone)] @@ -522,6 +547,8 @@ pub struct PaymentsCancelData { pub cancellation_reason: Option, pub connector_meta: Option, pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. } #[derive(Debug, Default, Clone)] @@ -572,7 +599,17 @@ pub struct AccessTokenRequestData { } pub trait Capturable { - fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option + where + F: Clone, + { + None + } + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + _attempt_status: common_enums::AttemptStatus, + ) -> Option where F: Clone, { @@ -581,7 +618,7 @@ pub trait Capturable { } impl Capturable for PaymentsAuthorizeData { - fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where F: Clone, { @@ -591,41 +628,171 @@ impl Capturable for PaymentsAuthorizeData { .map(|surcharge_details| surcharge_details.final_amount); final_amount.or(Some(self.amount)) } + + fn get_amount_capturable( + &self, + payment_data: &PaymentData, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + match payment_data + .payment_attempt + .capture_method + .unwrap_or_default() + { + common_enums::CaptureMethod::Automatic => { + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + match intent_status { + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Processing => Some(0), + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + }, + common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), + // In case of manual multiple, amount capturable must be inferred from all captures. + common_enums::CaptureMethod::ManualMultiple | + // Scheduled capture is not supported as of now + common_enums::CaptureMethod::Scheduled => None, + } + } } impl Capturable for PaymentsCaptureData { - fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where F: Clone, { Some(self.amount_to_capture) } + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + match intent_status { + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::Processing => Some(0), + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } } impl Capturable for CompleteAuthorizeData { - fn get_capture_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where F: Clone, { Some(self.amount) } + fn get_amount_capturable( + &self, + payment_data: &PaymentData, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + match payment_data + .payment_attempt + .capture_method + .unwrap_or_default() + { + common_enums::CaptureMethod::Automatic => { + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + match intent_status { + common_enums::IntentStatus::Succeeded| + common_enums::IntentStatus::Failed| + common_enums::IntentStatus::Processing => Some(0), + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + }, + common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), + // In case of manual multiple, amount capturable must be inferred from all captures. + common_enums::CaptureMethod::ManualMultiple | + // Scheduled capture is not supported as of now + common_enums::CaptureMethod::Scheduled => None, + } + } } impl Capturable for SetupMandateRequestData {} impl Capturable for PaymentsCancelData { - fn get_capture_amount(&self, payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where F: Clone, { // return previously captured amount payment_data.payment_intent.amount_captured } + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); + match intent_status { + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::Processing + | common_enums::IntentStatus::PartiallyCaptured => Some(0), + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } } impl Capturable for PaymentsApproveData {} impl Capturable for PaymentsRejectData {} impl Capturable for PaymentsSessionData {} -impl Capturable for PaymentsIncrementalAuthorizationData {} +impl Capturable for PaymentsIncrementalAuthorizationData { + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + _attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + Some(self.total_amount) + } +} impl Capturable for PaymentsSyncData { - fn get_capture_amount(&self, payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where F: Clone, { @@ -634,6 +801,20 @@ impl Capturable for PaymentsSyncData { .amount_to_capture .or_else(|| Some(payment_data.payment_attempt.get_total_amount())) } + fn get_amount_capturable( + &self, + _payment_data: &PaymentData, + attempt_status: common_enums::AttemptStatus, + ) -> Option + where + F: Clone, + { + if attempt_status.is_terminal_status() { + Some(0) + } else { + None + } + } } pub struct AddAccessTokenResult { @@ -946,6 +1127,17 @@ pub struct ResponseRouterData { pub http_code: u16, } +#[derive(Debug, Clone)] +pub struct MandateRevokeRequestData { + pub mandate_id: String, + pub connector_mandate_id: Option, +} + +#[derive(Debug, Clone)] +pub struct MandateRevokeResponseData { + pub mandate_status: MandateStatus, +} + // Different patterns of authentication. #[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(tag = "auth_type")] @@ -1235,6 +1427,7 @@ impl From<&SetupMandateRouterData> for PaymentsAuthorizeData { customer_id: None, surcharge_details: None, request_incremental_authorization: data.request.request_incremental_authorization, + metadata: None, } } } @@ -1282,6 +1475,7 @@ impl From<(&RouterData, T2)> connector_http_status_code: data.connector_http_status_code, external_latency: data.external_latency, apple_pay_flow: data.apple_pay_flow.clone(), + frm_metadata: data.frm_metadata.clone(), } } } @@ -1337,6 +1531,7 @@ impl connector_http_status_code: data.connector_http_status_code, external_latency: data.external_latency, apple_pay_flow: None, + frm_metadata: None, } } } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 978ce078faf..b60153eaf19 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -70,6 +70,18 @@ pub trait ConnectorVerifyWebhookSource: { } +#[derive(Clone, Debug)] +pub struct MandateRevoke; + +pub trait ConnectorMandateRevoke: + ConnectorIntegration< + MandateRevoke, + types::MandateRevokeRequestData, + types::MandateRevokeResponseData, +> +{ +} + pub trait ConnectorTransactionId: ConnectorCommon + Sync { fn connector_transaction_id( &self, @@ -159,6 +171,7 @@ pub trait Connector: + Payouts + ConnectorVerifyWebhookSource + FraudCheck + + ConnectorMandateRevoke { } @@ -179,7 +192,8 @@ impl< + ConnectorTransactionId + Payouts + ConnectorVerifyWebhookSource - + FraudCheck, + + FraudCheck + + ConnectorMandateRevoke, > Connector for T { } @@ -388,6 +402,7 @@ impl ConnectorData { // "payeezy" => Ok(Box::new(&connector::Payeezy)), As psync and rsync are not supported by this connector, it is added as template code for future usage enums::Connector::Payme => Ok(Box::new(&connector::Payme)), enums::Connector::Payu => Ok(Box::new(&connector::Payu)), + enums::Connector::Placetopay => Ok(Box::new(&connector::Placetopay)), enums::Connector::Powertranz => Ok(Box::new(&connector::Powertranz)), enums::Connector::Prophetpay => Ok(Box::new(&connector::Prophetpay)), enums::Connector::Rapyd => Ok(Box::new(&connector::Rapyd)), @@ -405,7 +420,9 @@ impl ConnectorData { enums::Connector::Tsys => Ok(Box::new(&connector::Tsys)), enums::Connector::Volt => Ok(Box::new(&connector::Volt)), enums::Connector::Zen => Ok(Box::new(&connector::Zen)), - enums::Connector::Signifyd | enums::Connector::Plaid => { + enums::Connector::Signifyd + | enums::Connector::Plaid + | enums::Connector::Riskified => { Err(report!(errors::ConnectorError::InvalidConnectorName) .attach_printable(format!("invalid connector name: {connector_name}"))) .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index fe99d084223..487cea88d15 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -14,6 +14,7 @@ use masking::Secret; use crate::{ core::errors, types::{domain, storage, transformers::ForeignTryFrom}, + utils::{self}, }; impl TryFrom for MerchantAccountResponse { @@ -46,7 +47,6 @@ impl TryFrom for MerchantAccountResponse { is_recon_enabled: item.is_recon_enabled, default_profile: item.default_profile, recon_status: item.recon_status, - payment_link_config: item.payment_link_config, }) } } @@ -72,6 +72,8 @@ impl ForeignTryFrom for BusinessProf frm_routing_algorithm: item.frm_routing_algorithm, payout_routing_algorithm: item.payout_routing_algorithm, applepay_verified_domains: item.applepay_verified_domains, + payment_link_config: item.payment_link_config, + session_expiry: item.session_expiry, }) } } @@ -105,6 +107,18 @@ impl ForeignTryFrom<(domain::MerchantAccount, BusinessProfileCreate)> .or(merchant_account.payment_response_hash_key) .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); + let payment_link_config_value = request + .payment_link_config + .map(|pl_config| { + utils::Encode::::encode_to_value( + &pl_config, + ) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config_value", + }) + }) + .transpose()?; + Ok(Self { profile_id, merchant_id: merchant_account.merchant_id, @@ -140,6 +154,11 @@ impl ForeignTryFrom<(domain::MerchantAccount, BusinessProfileCreate)> .or(merchant_account.payout_routing_algorithm), is_recon_enabled: merchant_account.is_recon_enabled, applepay_verified_domains: request.applepay_verified_domains, + payment_link_config: payment_link_config_value, + session_expiry: request + .session_expiry + .map(i64::from) + .or(Some(common_utils::consts::DEFAULT_SESSION_EXPIRY)), }) } } diff --git a/crates/router/src/types/api/connector_onboarding/paypal.rs b/crates/router/src/types/api/connector_onboarding/paypal.rs index 0cc026d4d7a..dbfdd6f5007 100644 --- a/crates/router/src/types/api/connector_onboarding/paypal.rs +++ b/crates/router/src/types/api/connector_onboarding/paypal.rs @@ -177,7 +177,7 @@ pub enum VettingStatus { impl SellerStatusResponse { pub fn extract_merchant_details_url(self, paypal_base_url: &str) -> RouterResult { self.links - .get(0) + .first() .and_then(|link| link.href.strip_prefix('/')) .map(|link| format!("{}{}", paypal_base_url, link)) .ok_or(ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 7be60bfee95..e871cbc5d33 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -86,6 +86,7 @@ impl FraudCheckConnectorData { ) -> CustomResult { match connector_name { enums::FrmConnectors::Signifyd => Ok(Box::new(&connector::Signifyd)), + enums::FrmConnectors::Riskified => Ok(Box::new(&connector::Riskified)), } } } diff --git a/crates/router/src/types/api/payment_link.rs b/crates/router/src/types/api/payment_link.rs index e56af6b4aec..85cb539d411 100644 --- a/crates/router/src/types/api/payment_link.rs +++ b/crates/router/src/types/api/payment_link.rs @@ -1,6 +1,7 @@ pub use api_models::payments::RetrievePaymentLinkResponse; use crate::{ + consts::DEFAULT_SESSION_EXPIRY, core::{errors::RouterResult, payment_link}, types::storage::{self}, }; @@ -13,7 +14,12 @@ pub(crate) trait PaymentLinkResponseExt: Sized { #[async_trait::async_trait] impl PaymentLinkResponseExt for RetrievePaymentLinkResponse { async fn from_db_payment_link(payment_link: storage::PaymentLink) -> RouterResult { - let status = payment_link::check_payment_link_status(payment_link.fulfilment_time); + let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| { + payment_link + .created_at + .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) + }); + let status = payment_link::check_payment_link_status(session_expiry); Ok(Self { link_to_pay: payment_link.link_to_pay, payment_link_id: payment_link.payment_link_id, @@ -21,7 +27,7 @@ impl PaymentLinkResponseExt for RetrievePaymentLinkResponse { description: payment_link.description, created_at: payment_link.created_at, merchant_id: payment_link.merchant_id, - link_expiry: payment_link.fulfilment_time, + expiry: payment_link.fulfilment_time, currency: payment_link.currency, status, }) diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index 5acb66b5068..ca852f832ee 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -1,4 +1,3 @@ -use api_models::enums as api_enums; pub use api_models::payment_methods::{ CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod, CustomerPaymentMethodsListResponse, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, @@ -9,9 +8,9 @@ pub use api_models::payment_methods::{ }; use error_stack::report; -use crate::{ - core::errors::{self, RouterResult}, - types::transformers::ForeignFrom, +use crate::core::{ + errors::{self, RouterResult}, + payments::helpers::validate_payment_method_type_against_payment_method, }; pub(crate) trait PaymentMethodCreateExt { @@ -21,16 +20,16 @@ pub(crate) trait PaymentMethodCreateExt { // convert self.payment_method_type to payment_method and compare it against self.payment_method impl PaymentMethodCreateExt for PaymentMethodCreate { fn validate(&self) -> RouterResult<()> { - let payment_method: Option = - self.payment_method_type.map(ForeignFrom::foreign_from); - if payment_method - .map(|payment_method| payment_method != self.payment_method) - .unwrap_or(false) - { - return Err(report!(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid 'payment_method_type' provided".to_string() - }) - .attach_printable("Invalid payment method type")); + if let Some(payment_method_type) = self.payment_method_type { + if !validate_payment_method_type_against_payment_method( + self.payment_method, + payment_method_type, + ) { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid 'payment_method_type' provided".to_string() + }) + .attach_printable("Invalid payment method type")); + } } Ok(()) } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 2acf42fa479..9159abf4bd1 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -244,7 +244,7 @@ mod payments_test { card_number: "1234432112344321".to_string().try_into().unwrap(), card_exp_month: "12".to_string().into(), card_exp_year: "99".to_string().into(), - card_holder_name: "JohnDoe".to_string().into(), + card_holder_name: Some(masking::Secret::new("JohnDoe".to_string())), card_cvc: "123".to_string().into(), card_issuer: Some("HDFC".to_string()), card_network: Some(api_models::enums::CardNetwork::Visa), diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 74b15f911b9..c5fcce8b185 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -27,6 +27,7 @@ impl VerifyConnectorData { amount: 1000, confirm: true, currency: storage_enums::Currency::USD, + metadata: None, mandate_id: None, webhook_url: None, customer_id: None, @@ -100,6 +101,7 @@ impl VerifyConnectorData { connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, } } } diff --git a/crates/router/src/types/domain/merchant_account.rs b/crates/router/src/types/domain/merchant_account.rs index cbcd70aaef1..3832ffb3da7 100644 --- a/crates/router/src/types/domain/merchant_account.rs +++ b/crates/router/src/types/domain/merchant_account.rs @@ -79,6 +79,7 @@ pub enum MerchantAccountUpdate { recon_status: diesel_models::enums::ReconStatus, }, UnsetDefaultProfile, + ModifiedAtUpdate, } impl From for MerchantAccountUpdateInternal { @@ -140,6 +141,10 @@ impl From for MerchantAccountUpdateInternal { default_profile: Some(None), ..Default::default() }, + MerchantAccountUpdate::ModifiedAtUpdate => Self { + modified_at: Some(date_time::now()), + ..Default::default() + }, } } } diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 9bc27cba2b1..d271ed5e29d 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -344,10 +344,8 @@ impl NewUserMerchant { merchant_details: None, routing_algorithm: None, parent_merchant_id: None, - payment_link_config: None, sub_merchants_enabled: None, frm_routing_algorithm: None, - intent_fulfillment_time: None, payout_routing_algorithm: None, primary_business_details: None, payment_response_hash_key: None, @@ -491,6 +489,10 @@ impl NewUser { self.new_merchant.clone() } + pub fn get_password(&self) -> UserPassword { + self.password.clone() + } + pub async fn insert_user_in_db( &self, db: &dyn StorageInterface, @@ -556,7 +558,7 @@ impl NewUser { user_id, role_id, created_at: now, - last_modified_at: now, + last_modified: now, org_id: self .get_new_merchant() .get_new_organization() @@ -685,8 +687,7 @@ impl TryFrom for NewUser { let user_id = uuid::Uuid::new_v4().to_string(); let email = value.0.email.clone().try_into()?; let name = UserName::new(value.0.name.clone())?; - let password = password::generate_password_hash(uuid::Uuid::new_v4().to_string().into())?; - let password = UserPassword::new(password)?; + let password = UserPassword::new(uuid::Uuid::new_v4().to_string().into())?; let new_merchant = NewUserMerchant::try_from(value)?; Ok(Self { @@ -788,6 +789,7 @@ impl From for user_role_api::PermissionModule { info::PermissionModule::Routing => Self::Routing, info::PermissionModule::Analytics => Self::Analytics, info::PermissionModule::Mandates => Self::Mandates, + info::PermissionModule::Customer => Self::Customer, info::PermissionModule::Disputes => Self::Disputes, info::PermissionModule::Files => Self::Files, info::PermissionModule::ThreeDsDecisionManager => Self::ThreeDsDecisionManager, @@ -829,7 +831,7 @@ impl TryFrom for user_api::UserDetails { role_id, status, role_name, - last_modified_at: user_and_role.1.last_modified_at, + last_modified_at: user_and_role.0.last_modified_at, }) } } diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs index 4bbba8ac4dc..191e74422f9 100644 --- a/crates/router/src/types/fraud_check.rs +++ b/crates/router/src/types/fraud_check.rs @@ -1,9 +1,13 @@ +use common_utils::pii::Email; + use crate::{ - connector::signifyd::transformers::{FrmFullfillmentSignifydApiRequest, RefundMethod}, + connector::signifyd::transformers::RefundMethod, + core::fraud_check::types::FrmFulfillmentRequest, pii::Serialize, services, - types::{api, storage_enums, ErrorResponse, ResponseId, RouterData}, + types::{self, api, storage_enums, ErrorResponse, ResponseId, RouterData}, }; + pub type FrmSaleRouterData = RouterData; pub type FrmSaleType = @@ -74,6 +78,11 @@ pub type FrmCheckoutType = dyn services::ConnectorIntegration< pub struct FraudCheckCheckoutData { pub amount: i64, pub order_details: Option>, + pub currency: Option, + pub browser_info: Option, + pub payment_method_data: Option, + pub email: Option, + pub gateway: Option, } pub type FrmTransactionRouterData = @@ -91,6 +100,9 @@ pub struct FraudCheckTransactionData { pub order_details: Option>, pub currency: Option, pub payment_method: Option, + pub error_code: Option, + pub error_message: Option, + pub connector_transaction_id: Option, } pub type FrmFulfillmentRouterData = @@ -114,7 +126,7 @@ pub type FrmRecordReturnType = dyn services::ConnectorIntegration< pub struct FraudCheckFulfillmentData { pub amount: i64, pub order_details: Option>>, - pub fulfillment_request: FrmFullfillmentSignifydApiRequest, + pub fulfillment_req: FrmFulfillmentRequest, } #[derive(Debug, Clone)] diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index 1dc241cde20..b93cbbbbba9 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -1,6 +1,9 @@ pub mod address; pub mod api_keys; pub mod authorization; +pub mod blocklist; +pub mod blocklist_fingerprint; +pub mod blocklist_lookup; pub mod business_profile; pub mod capture; pub mod cards_info; @@ -26,7 +29,6 @@ pub mod payment_link; pub mod payment_method; pub mod payout_attempt; pub mod payouts; -mod query; pub mod refund; pub mod reverse_lookup; pub mod routing_algorithm; @@ -44,7 +46,8 @@ pub use diesel_models::{ProcessTracker, ProcessTrackerNew, ProcessTrackerUpdate} pub use scheduler::db::process_tracker; pub use self::{ - address::*, api_keys::*, authorization::*, capture::*, cards_info::*, configs::*, customers::*, + address::*, api_keys::*, authorization::*, blocklist::*, blocklist_fingerprint::*, + blocklist_lookup::*, capture::*, cards_info::*, configs::*, customers::*, dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, file::*, fraud_check::*, gsm::*, locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*, payout_attempt::*, payouts::*, diff --git a/crates/router/src/types/storage/blocklist.rs b/crates/router/src/types/storage/blocklist.rs new file mode 100644 index 00000000000..7e7648dd4a0 --- /dev/null +++ b/crates/router/src/types/storage/blocklist.rs @@ -0,0 +1 @@ +pub use diesel_models::blocklist::{Blocklist, BlocklistNew}; diff --git a/crates/router/src/types/storage/blocklist_fingerprint.rs b/crates/router/src/types/storage/blocklist_fingerprint.rs new file mode 100644 index 00000000000..092d881e3fa --- /dev/null +++ b/crates/router/src/types/storage/blocklist_fingerprint.rs @@ -0,0 +1 @@ +pub use diesel_models::blocklist_fingerprint::{BlocklistFingerprint, BlocklistFingerprintNew}; diff --git a/crates/router/src/types/storage/blocklist_lookup.rs b/crates/router/src/types/storage/blocklist_lookup.rs new file mode 100644 index 00000000000..978708ff7c3 --- /dev/null +++ b/crates/router/src/types/storage/blocklist_lookup.rs @@ -0,0 +1 @@ +pub use diesel_models::blocklist_lookup::{BlocklistLookup, BlocklistLookupNew}; diff --git a/crates/router/src/types/storage/query.rs b/crates/router/src/types/storage/query.rs deleted file mode 100644 index 1483dec3eab..00000000000 --- a/crates/router/src/types/storage/query.rs +++ /dev/null @@ -1 +0,0 @@ -pub use diesel_models::query::*; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 34ae3dceb5a..786a8c55182 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -212,6 +212,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Payme => Self::Payme, api_enums::Connector::Paypal => Self::Paypal, api_enums::Connector::Payu => Self::Payu, + api_models::enums::Connector::Placetopay => Self::Placetopay, api_enums::Connector::Plaid => { Err(common_utils::errors::ValidationError::InvalidValue { message: "plaid is not a routable connector".to_string(), @@ -228,6 +229,12 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { }) .into_report()? } + api_enums::Connector::Riskified => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "riskified is not a routable connector".to_string(), + }) + .into_report()? + } api_enums::Connector::Square => Self::Square, api_enums::Connector::Stax => Self::Stax, api_enums::Connector::Stripe => Self::Stripe, @@ -345,11 +352,15 @@ impl ForeignFrom for Option { Some(storage_enums::EventType::ActionRequired) } api_enums::IntentStatus::Cancelled => Some(storage_enums::EventType::PaymentCancelled), + api_enums::IntentStatus::PartiallyCaptured + | api_enums::IntentStatus::PartiallyCapturedAndCapturable => { + Some(storage_enums::EventType::PaymentCaptured) + } + api_enums::IntentStatus::RequiresCapture => { + Some(storage_enums::EventType::PaymentAuthorized) + } api_enums::IntentStatus::RequiresPaymentMethod - | api_enums::IntentStatus::RequiresConfirmation - | api_enums::IntentStatus::RequiresCapture - | api_enums::IntentStatus::PartiallyCaptured - | api_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + | api_enums::IntentStatus::RequiresConfirmation => None, } } } @@ -936,19 +947,27 @@ impl } } -impl ForeignFrom<(storage::PaymentLink, String)> - for api_models::payments::RetrievePaymentLinkResponse +impl + ForeignFrom<( + storage::PaymentLink, + api_models::payments::PaymentLinkStatus, + )> for api_models::payments::RetrievePaymentLinkResponse { - fn foreign_from((payment_link_object, status): (storage::PaymentLink, String)) -> Self { + fn foreign_from( + (payment_link_config, status): ( + storage::PaymentLink, + api_models::payments::PaymentLinkStatus, + ), + ) -> Self { Self { - payment_link_id: payment_link_object.payment_link_id, - merchant_id: payment_link_object.merchant_id, - link_to_pay: payment_link_object.link_to_pay, - amount: payment_link_object.amount, - created_at: payment_link_object.created_at, - link_expiry: payment_link_object.fulfilment_time, - description: payment_link_object.description, - currency: payment_link_object.currency, + payment_link_id: payment_link_config.payment_link_id, + merchant_id: payment_link_config.merchant_id, + link_to_pay: payment_link_config.link_to_pay, + amount: payment_link_config.amount, + created_at: payment_link_config.created_at, + expiry: payment_link_config.fulfilment_time, + description: payment_link_config.description, + currency: payment_link_config.currency, status, } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 42116e1ecbf..aaa145099e4 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -196,16 +196,6 @@ impl QrImage { } } -#[cfg(test)] -mod tests { - use crate::utils; - #[test] - fn test_image_data_source_url() { - let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); - assert!(qr_image_data_source_url.is_ok()); - } -} - pub async fn find_payment_intent_from_payment_id_type( db: &dyn StorageInterface, payment_id_type: payments::PaymentIdType, @@ -804,3 +794,13 @@ pub async fn flatten_join_error(handle: Handle) -> RouterResult { .attach_printable("Join Error"), } } + +#[cfg(test)] +mod tests { + use crate::utils; + #[test] + fn test_image_data_source_url() { + let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); + assert!(qr_image_data_source_url.is_ok()); + } +} diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs index e8afcd68a46..03735e61cc7 100644 --- a/crates/router/src/utils/connector_onboarding.rs +++ b/crates/router/src/utils/connector_onboarding.rs @@ -1,6 +1,11 @@ +use diesel_models::{ConfigNew, ConfigUpdate}; +use error_stack::ResultExt; + +use super::errors::StorageErrorExt; use crate::{ + consts, core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, - routes::app::settings, + routes::{app::settings, AppState}, types::{self, api::enums}, }; @@ -34,3 +39,105 @@ pub fn is_enabled( _ => None, } } + +pub async fn check_if_connector_exists( + state: &AppState, + connector_id: &str, + merchant_id: &str, +) -> RouterResult<()> { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?; + + let _connector = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + merchant_id, + connector_id, + &key_store, + ) + .await + .to_not_found_response(ApiErrorResponse::MerchantConnectorAccountNotFound { + id: connector_id.to_string(), + })?; + + Ok(()) +} + +pub async fn set_tracking_id_in_configs( + state: &AppState, + connector_id: &str, + connector: enums::Connector, +) -> RouterResult<()> { + let timestamp = common_utils::date_time::now_unix_timestamp().to_string(); + let find_config = state + .store + .find_config_by_key(&build_key(connector_id, connector)) + .await; + + if find_config.is_ok() { + state + .store + .update_config_by_key( + &build_key(connector_id, connector), + ConfigUpdate::Update { + config: Some(timestamp), + }, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error updating data in configs table")?; + } else if find_config + .as_ref() + .map_err(|e| e.current_context().is_db_not_found()) + .err() + .unwrap_or(false) + { + state + .store + .insert_config(ConfigNew { + key: build_key(connector_id, connector), + config: timestamp, + }) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error inserting data in configs table")?; + } else { + find_config.change_context(ApiErrorResponse::InternalServerError)?; + } + + Ok(()) +} + +pub async fn get_tracking_id_from_configs( + state: &AppState, + connector_id: &str, + connector: enums::Connector, +) -> RouterResult { + let timestamp = state + .store + .find_config_by_key_unwrap_or( + &build_key(connector_id, connector), + Some(common_utils::date_time::now_unix_timestamp().to_string()), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error getting data from configs table")? + .config; + + Ok(format!("{}_{}", connector_id, timestamp)) +} + +fn build_key(connector_id: &str, connector: enums::Connector) -> String { + format!( + "{}_{}_{}", + consts::CONNECTOR_ONBOARDING_CONFIG_PREFIX, + connector, + connector_id, + ) +} diff --git a/crates/router/src/utils/connector_onboarding/paypal.rs b/crates/router/src/utils/connector_onboarding/paypal.rs index c803775be07..6d7f2692be7 100644 --- a/crates/router/src/utils/connector_onboarding/paypal.rs +++ b/crates/router/src/utils/connector_onboarding/paypal.rs @@ -1,10 +1,6 @@ -use common_utils::{ - ext_traits::Encode, - request::{Method, Request, RequestBuilder}, -}; +use common_utils::request::{Method, Request, RequestBuilder, RequestContent}; use error_stack::{IntoReport, ResultExt}; use http::header; -use serde_json::json; use crate::{ connector, @@ -51,15 +47,8 @@ pub fn build_paypal_post_request( access_token: String, ) -> RouterResult where - T: serde::Serialize, + T: serde::Serialize + Send + 'static, { - let body = types::RequestBody::log_and_get_request_body( - &json!(body), - Encode::::encode_to_string_of_json, - ) - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to build request body")?; - Ok(RequestBuilder::new() .method(Method::Post) .url(&url) @@ -72,7 +61,7 @@ where header::CONTENT_TYPE.to_string().as_str(), "application/json", ) - .body(Some(body)) + .set_body(RequestContent::Json(Box::new(body))) .build()) } diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 5f765028014..a115fa2a2d8 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -1,5 +1,5 @@ use api_models::user as user_api; -use diesel_models::{enums::UserStatus, user_role::UserRole}; +use diesel_models::user_role::UserRole; use error_stack::ResultExt; use masking::Secret; @@ -55,22 +55,6 @@ impl UserFromToken { } } -pub async fn get_merchant_ids_for_user(state: AppState, user_id: &str) -> UserResult> { - Ok(state - .store - .list_user_roles_by_user_id(user_id) - .await - .change_context(UserErrors::InternalServerError)? - .into_iter() - .filter_map(|ele| { - if ele.status == UserStatus::Active { - return Some(ele.merchant_id); - } - None - }) - .collect()) -} - pub async fn generate_jwt_auth_token( state: AppState, user: &UserFromStorage, @@ -87,18 +71,19 @@ pub async fn generate_jwt_auth_token( Ok(Secret::new(token)) } -pub async fn generate_jwt_auth_token_with_custom_merchant_id( +pub async fn generate_jwt_auth_token_with_custom_role_attributes( state: AppState, user: &UserFromStorage, - user_role: &UserRole, merchant_id: String, + org_id: String, + role_id: String, ) -> UserResult> { let token = AuthToken::new_token( user.get_user_id().to_string(), merchant_id, - user_role.role_id.clone(), + role_id, &state.conf, - user_role.org_id.to_owned(), + org_id, ) .await?; Ok(Secret::new(token)) diff --git a/crates/router/src/utils/user/dashboard_metadata.rs b/crates/router/src/utils/user/dashboard_metadata.rs index 40594a6e49f..09fb5ccd24b 100644 --- a/crates/router/src/utils/user/dashboard_metadata.rs +++ b/crates/router/src/utils/user/dashboard_metadata.rs @@ -91,21 +91,12 @@ pub async fn get_merchant_scoped_metadata_from_db( org_id: String, metadata_keys: Vec, ) -> UserResult> { - match state + state .store .find_merchant_scoped_dashboard_metadata(&merchant_id, &org_id, metadata_keys) .await - { - Ok(data) => Ok(data), - Err(e) => { - if e.current_context().is_db_not_found() { - return Ok(Vec::with_capacity(0)); - } - Err(e - .change_context(UserErrors::InternalServerError) - .attach_printable("DB Error Fetching DashboardMetaData")) - } - } + .change_context(UserErrors::InternalServerError) + .attach_printable("DB Error Fetching DashboardMetaData") } pub async fn get_user_scoped_metadata_from_db( state: &AppState, diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 9f95e2d078d..3fa2a10629e 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -48,11 +48,11 @@ pub async fn generate_sample_data( .change_context(SampleDataError::InternalServerError) .attach_printable("Error while parsing primary business details")?; - let business_country_default = merchant_parsed_details.get(0).map(|x| x.country); + let business_country_default = merchant_parsed_details.first().map(|x| x.country); - let business_label_default = merchant_parsed_details.get(0).map(|x| x.business.clone()); + let business_label_default = merchant_parsed_details.first().map(|x| x.business.clone()); - let profile_id = crate::core::utils::get_profile_id_from_business_details( + let profile_id = match crate::core::utils::get_profile_id_from_business_details( business_country_default, business_label_default.as_ref(), &merchant_from_db, @@ -61,8 +61,25 @@ pub async fn generate_sample_data( false, ) .await - .change_context(SampleDataError::InternalServerError) - .attach_printable("Failed to get business profile")?; + { + Ok(id) => id.clone(), + Err(error) => { + router_env::logger::error!( + "Profile ID not found in business details. Attempting to fetch from the database {error:?}" + ); + + state + .store + .list_business_profile_by_merchant_id(&merchant_id) + .await + .change_context(SampleDataError::InternalServerError) + .attach_printable("Failed to get business profile")? + .first() + .ok_or(SampleDataError::InternalServerError)? + .profile_id + .clone() + } + }; // 10 percent payments should be failed #[allow(clippy::as_conversions)] @@ -146,6 +163,8 @@ pub async fn generate_sample_data( common_utils::date_time::now() - time::Duration::days(7) }), ); + let session_expiry = + created_at.saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)); // After some set of payments sample data will have a failed attempt let is_failed_payment = @@ -197,6 +216,8 @@ pub async fn generate_sample_data( request_incremental_authorization: Default::default(), incremental_authorization_allowed: Default::default(), authorization_count: Default::default(), + fingerprint_id: None, + session_expiry: Some(session_expiry), }; let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 0026984fdb9..c474a82981b 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -18,7 +18,7 @@ pub fn is_internal_role(role_id: &str) -> bool { || role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER } -pub async fn get_merchant_ids_for_user(state: AppState, user_id: &str) -> UserResult> { +pub async fn get_merchant_ids_for_user(state: &AppState, user_id: &str) -> UserResult> { Ok(state .store .list_user_roles_by_user_id(user_id) @@ -74,6 +74,8 @@ impl TryFrom<&Permission> for user_role_api::Permission { Permission::DisputeWrite => Ok(Self::DisputeWrite), Permission::MandateRead => Ok(Self::MandateRead), Permission::MandateWrite => Ok(Self::MandateWrite), + Permission::CustomerRead => Ok(Self::CustomerRead), + Permission::CustomerWrite => Ok(Self::CustomerWrite), Permission::FileRead => Ok(Self::FileRead), Permission::FileWrite => Ok(Self::FileWrite), Permission::Analytics => Ok(Self::Analytics), diff --git a/crates/router/src/utils/verify_connector.rs b/crates/router/src/utils/verify_connector.rs index 6ad683d63ba..060f92d0e5a 100644 --- a/crates/router/src/utils/verify_connector.rs +++ b/crates/router/src/utils/verify_connector.rs @@ -20,7 +20,7 @@ pub fn generate_card_from_details( card_network: None, card_exp_year: masking::Secret::new(card_exp_year), card_exp_month: masking::Secret::new(card_exp_month), - card_holder_name: masking::Secret::new("HyperSwitch".to_string()), + card_holder_name: Some(masking::Secret::new("HyperSwitch".to_string())), nick_name: None, card_type: None, card_issuing_country: None, diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 43567ce27e2..b2296e17f70 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -301,7 +301,10 @@ mod tests { let cpt_default = process_data::ConnectorPTMapping::default().default_mapping; assert_eq!( vec![schedule_time_delta, first_retry_time_delta], - vec![cpt_default.start_after, cpt_default.frequency[0]] + vec![ + cpt_default.start_after, + *cpt_default.frequency.first().unwrap() + ] ); } } diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 4de45c7132a..040e0dddf97 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -50,8 +50,8 @@ async fn invalidate_existing_cache_success() { let response_body = response.body().await; println!("invalidate Cache: {response:?} : {response_body:?}"); assert_eq!(response.status(), awc::http::StatusCode::OK); - assert!(cache::CONFIG_CACHE.get(&cache_key).is_none()); - assert!(cache::ACCOUNTS_CACHE.get(&cache_key).is_none()); + assert!(cache::CONFIG_CACHE.get(&cache_key).await.is_none()); + assert!(cache::ACCOUNTS_CACHE.get(&cache_key).await.is_none()); } #[actix_web::test] diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 7ddc504956f..35c9cbd952d 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -38,7 +38,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { card_number: cards::CardNumber::from_str("4200000000000000").unwrap(), card_exp_month: Secret::new("10".to_string()), card_exp_year: Secret::new("2025".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("999".to_string()), card_issuer: None, card_network: None, @@ -70,6 +70,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }, response: Err(types::ErrorResponse::default()), payment_method_id: None, @@ -95,6 +96,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } @@ -153,6 +155,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } @@ -232,7 +235,7 @@ async fn payments_create_failure() { card_number: cards::CardNumber::from_str("4200000000000000").unwrap(), card_exp_month: Secret::new("10".to_string()), card_exp_year: Secret::new("2025".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("99".to_string()), card_issuer: None, card_network: None, diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 714dc0d7d67..49075080506 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -95,16 +95,16 @@ impl AdyenTest { card_number: cards::CardNumber::from_str("4111111111111111").unwrap(), expiry_month: Secret::new("3".to_string()), expiry_year: Secret::new("2030".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(Secret::new("John Doe".to_string())), })) } enums::PayoutType::Bank => Some(api::PayoutMethodData::Bank( api::payouts::BankPayout::Sepa(api::SepaBankTransfer { iban: "NL46TEST0136169112".to_string().into(), bic: Some("ABNANL2A".to_string().into()), - bank_name: "Deutsche Bank".to_string(), - bank_country_code: enums::CountryAlpha2::NL, - bank_city: "Amsterdam".to_string(), + bank_name: Some("Deutsche Bank".to_string()), + bank_country_code: Some(enums::CountryAlpha2::NL), + bank_city: Some("Amsterdam".to_string()), }), )), }, @@ -126,7 +126,7 @@ impl AdyenTest { card_number: cards::CardNumber::from_str(card_number).unwrap(), card_exp_month: Secret::new(card_exp_month.to_string()), card_exp_year: Secret::new(card_exp_year.to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new(card_cvc.to_string()), card_issuer: None, card_network: None, @@ -158,6 +158,7 @@ impl AdyenTest { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } } diff --git a/crates/router/tests/connectors/airwallex.rs b/crates/router/tests/connectors/airwallex.rs index 6e7f6c000d2..cfc4c0c003d 100644 --- a/crates/router/tests/connectors/airwallex.rs +++ b/crates/router/tests/connectors/airwallex.rs @@ -60,7 +60,7 @@ fn payment_method_details() -> Option { card_number: cards::CardNumber::from_str("4035501000000008").unwrap(), card_exp_month: Secret::new("02".to_string()), card_exp_year: Secret::new("2035".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("123".to_string()), card_issuer: None, card_network: None, diff --git a/crates/router/tests/connectors/authorizedotnet.rs b/crates/router/tests/connectors/authorizedotnet.rs index 4021d57d543..3ae4298e836 100644 --- a/crates/router/tests/connectors/authorizedotnet.rs +++ b/crates/router/tests/connectors/authorizedotnet.rs @@ -42,7 +42,7 @@ fn get_payment_method_data() -> api::Card { card_number: cards::CardNumber::from_str("5424000000000015").unwrap(), card_exp_month: Secret::new("02".to_string()), card_exp_year: Secret::new("2035".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("123".to_string()), ..Default::default() } diff --git a/crates/router/tests/connectors/bitpay.rs b/crates/router/tests/connectors/bitpay.rs index 3c9f08bf1b6..8bac7c13c85 100644 --- a/crates/router/tests/connectors/bitpay.rs +++ b/crates/router/tests/connectors/bitpay.rs @@ -93,6 +93,7 @@ fn payment_method_details() -> Option { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } diff --git a/crates/router/tests/connectors/bluesnap.rs b/crates/router/tests/connectors/bluesnap.rs index 30052d11da4..852b23f022c 100644 --- a/crates/router/tests/connectors/bluesnap.rs +++ b/crates/router/tests/connectors/bluesnap.rs @@ -400,7 +400,7 @@ async fn should_fail_payment_for_incorrect_cvc() { Some(types::PaymentsAuthorizeData { email: Some(Email::from_str("test@gmail.com").unwrap()), payment_method_data: types::api::PaymentMethodData::Card(api::Card { - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("12345".to_string()), ..utils::CCardType::default().0 }), @@ -426,7 +426,7 @@ async fn should_fail_payment_for_invalid_exp_month() { Some(types::PaymentsAuthorizeData { email: Some(Email::from_str("test@gmail.com").unwrap()), payment_method_data: types::api::PaymentMethodData::Card(api::Card { - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_exp_month: Secret::new("20".to_string()), ..utils::CCardType::default().0 }), @@ -452,7 +452,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { Some(types::PaymentsAuthorizeData { email: Some(Email::from_str("test@gmail.com").unwrap()), payment_method_data: types::api::PaymentMethodData::Card(api::Card { - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_exp_year: Secret::new("2000".to_string()), ..utils::CCardType::default().0 }), diff --git a/crates/router/tests/connectors/cashtocode.rs b/crates/router/tests/connectors/cashtocode.rs index a7c95936fbe..68c4eb94bf3 100644 --- a/crates/router/tests/connectors/cashtocode.rs +++ b/crates/router/tests/connectors/cashtocode.rs @@ -68,6 +68,7 @@ impl CashtocodeTest { customer_id: Some("John Doe".to_owned()), surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } diff --git a/crates/router/tests/connectors/coinbase.rs b/crates/router/tests/connectors/coinbase.rs index 2ddb5464d4d..73ee93178c0 100644 --- a/crates/router/tests/connectors/coinbase.rs +++ b/crates/router/tests/connectors/coinbase.rs @@ -95,6 +95,7 @@ fn payment_method_details() -> Option { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } diff --git a/crates/router/tests/connectors/cryptopay.rs b/crates/router/tests/connectors/cryptopay.rs index 11e556215c3..5df8d80461f 100644 --- a/crates/router/tests/connectors/cryptopay.rs +++ b/crates/router/tests/connectors/cryptopay.rs @@ -93,6 +93,7 @@ fn payment_method_details() -> Option { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } diff --git a/crates/router/tests/connectors/fiserv.rs b/crates/router/tests/connectors/fiserv.rs index 1394667718c..36d5f66dbcc 100644 --- a/crates/router/tests/connectors/fiserv.rs +++ b/crates/router/tests/connectors/fiserv.rs @@ -46,7 +46,7 @@ fn payment_method_details() -> Option { card_number: cards::CardNumber::from_str("4005550000000019").unwrap(), card_exp_month: Secret::new("02".to_string()), card_exp_year: Secret::new("2035".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("123".to_string()), card_issuer: None, card_network: None, diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index fc474818b50..8db743d6098 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -45,6 +45,7 @@ mod payeezy; mod payme; mod paypal; mod payu; +mod placetopay; mod powertranz; #[cfg(feature = "dummy_connector")] mod prophetpay; diff --git a/crates/router/tests/connectors/opennode.rs b/crates/router/tests/connectors/opennode.rs index 707192e01c3..b140a7c0517 100644 --- a/crates/router/tests/connectors/opennode.rs +++ b/crates/router/tests/connectors/opennode.rs @@ -94,6 +94,7 @@ fn payment_method_details() -> Option { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index 5550ba12af8..206694be0e8 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -78,6 +78,11 @@ fn payment_method_details() -> Option { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -87,7 +92,7 @@ fn payment_method_details() -> Option { card_cvc: Secret::new("123".to_string()), card_exp_month: Secret::new("10".to_string()), card_exp_year: Secret::new("2025".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), ..utils::CCardType::default().0 }), amount: 1000, @@ -373,6 +378,11 @@ async fn should_fail_payment_for_incorrect_cvc() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -406,6 +416,11 @@ async fn should_fail_payment_for_invalid_exp_month() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -439,6 +454,11 @@ async fn should_fail_payment_for_incorrect_expiry_year() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), diff --git a/crates/router/tests/connectors/placetopay.rs b/crates/router/tests/connectors/placetopay.rs new file mode 100644 index 00000000000..b7c70c789da --- /dev/null +++ b/crates/router/tests/connectors/placetopay.rs @@ -0,0 +1,420 @@ +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct PlacetopayTest; +impl ConnectorActions for PlacetopayTest {} +impl utils::Connector for PlacetopayTest { + fn get_data(&self) -> types::api::ConnectorData { + use router::connector::Placetopay; + types::api::ConnectorData { + connector: Box::new(&Placetopay), + connector_name: types::Connector::Placetopay, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .placetopay + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "placetopay".to_string() + } +} + +static CONNECTOR: PlacetopayTest = PlacetopayTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenerios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/rapyd.rs b/crates/router/tests/connectors/rapyd.rs index 143f87fc575..c53f4e8d8b1 100644 --- a/crates/router/tests/connectors/rapyd.rs +++ b/crates/router/tests/connectors/rapyd.rs @@ -46,7 +46,7 @@ async fn should_only_authorize_payment() { card_number: cards::CardNumber::from_str("4111111111111111").unwrap(), card_exp_month: Secret::new("02".to_string()), card_exp_year: Secret::new("2024".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("123".to_string()), card_issuer: None, card_network: None, @@ -74,7 +74,7 @@ async fn should_authorize_and_capture_payment() { card_number: cards::CardNumber::from_str("4111111111111111").unwrap(), card_exp_month: Secret::new("02".to_string()), card_exp_year: Secret::new("2024".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("123".to_string()), card_issuer: None, card_network: None, diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index f8f6039d6d3..68cf6f68035 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -108,7 +108,7 @@ api_key = "API Key" [iatapay] key1 = "key1" api_key = "api_key" -api_secret = "secrect" +api_secret = "secret" [dummyconnector] api_key = "API Key" @@ -189,3 +189,7 @@ api_key="API Key" api_key = "MyApiKey" key1 = "Merchant id" api_secret = "Secret key" + +[placetopay] +api_key= "Login" +key1= "Trankey" diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 7e5cfeb4397..db82cd7e032 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -458,7 +458,7 @@ pub trait ConnectorActions: Connector { customer_details: Some(payments::CustomerDetails { customer_id: core_utils::get_or_generate_id("customer_id", &None, "cust_").ok(), name: Some(Secret::new("John Doe".to_string())), - email: TryFrom::try_from(Secret::new("john.doe@example".to_string())).ok(), + email: Email::from_str("john.doe@example").ok(), phone: Some(Secret::new("620874518".to_string())), phone_country_code: Some("+31".to_string()), }), @@ -523,6 +523,7 @@ pub trait ConnectorActions: Connector { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } @@ -869,7 +870,7 @@ impl Default for CCardType { card_number: cards::CardNumber::from_str("4200000000000000").unwrap(), card_exp_month: Secret::new("10".to_string()), card_exp_year: Secret::new("2025".to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new("999".to_string()), card_issuer: None, card_network: None, @@ -910,6 +911,7 @@ impl Default for PaymentAuthorizeType { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }; Self(data) } @@ -994,7 +996,7 @@ impl Default for CustomerType { let data = types::ConnectorCustomerData { payment_method_data: types::api::PaymentMethodData::Card(CCardType::default().0), description: None, - email: Some(Email::from(Secret::new("test@juspay.in".to_string()))), + email: Email::from_str("test@juspay.in").ok(), phone: None, name: None, preprocessing_id: None, diff --git a/crates/router/tests/connectors/wise.rs b/crates/router/tests/connectors/wise.rs index fb65397e1a2..de303523040 100644 --- a/crates/router/tests/connectors/wise.rs +++ b/crates/router/tests/connectors/wise.rs @@ -73,9 +73,9 @@ impl WiseTest { api::BacsBankTransfer { bank_sort_code: "231470".to_string().into(), bank_account_number: "28821822".to_string().into(), - bank_name: "Deutsche Bank".to_string(), - bank_country_code: enums::CountryAlpha2::NL, - bank_city: "Amsterdam".to_string(), + bank_name: Some("Deutsche Bank".to_string()), + bank_country_code: Some(enums::CountryAlpha2::NL), + bank_city: Some("Amsterdam".to_string()), }, ))), ..Default::default() diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index fd697f95b75..4f7a94780a5 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -71,7 +71,7 @@ impl WorldlineTest { card_number: cards::CardNumber::from_str(card_number).unwrap(), card_exp_month: Secret::new(card_exp_month.to_string()), card_exp_year: Secret::new(card_exp_year.to_string()), - card_holder_name: Secret::new("John Doe".to_string()), + card_holder_name: Some(masking::Secret::new("John Doe".to_string())), card_cvc: Secret::new(card_cvc.to_string()), card_issuer: None, card_network: None, @@ -103,6 +103,7 @@ impl WorldlineTest { customer_id: None, surcharge_details: None, request_incremental_authorization: false, + metadata: None, }) } } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 12f914e13c1..c3bce7d51c3 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -315,6 +315,11 @@ async fn should_fail_payment_for_incorrect_card_number() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -351,6 +356,11 @@ async fn should_fail_payment_for_incorrect_cvc() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -387,6 +397,11 @@ async fn should_fail_payment_for_invalid_exp_month() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -423,6 +438,11 @@ async fn should_fail_payment_for_incorrect_expiry_year() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), diff --git a/crates/router/tests/integration_demo.rs b/crates/router/tests/integration_demo.rs index 5bdf9a5f525..777e0a68298 100644 --- a/crates/router/tests/integration_demo.rs +++ b/crates/router/tests/integration_demo.rs @@ -153,7 +153,7 @@ async fn exceed_refund() { let message: serde_json::Value = user_client.create_refund(&server, &payment_id, 100).await; assert_eq!( - message["error"]["message"], - "Refund amount exceeds the payment amount." + message.get("error").unwrap().get("message").unwrap(), + "The refund amount exceeds the amount captured." ); } diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 9d48aaddd45..8f5ac25736c 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -320,7 +320,7 @@ async fn payments_create_core() { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), - card_holder_name: "Arun Raj".to_string().into(), + card_holder_name: Some(masking::Secret::new("Arun Raj".to_string())), card_cvc: "123".to_string().into(), card_issuer: None, card_network: None, @@ -496,7 +496,7 @@ async fn payments_create_core_adyen_no_redirect() { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), - card_holder_name: "JohnDoe".to_string().into(), + card_holder_name: Some(masking::Secret::new("JohnDoe".to_string())), card_cvc: "737".to_string().into(), card_issuer: None, card_network: None, diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 42e5524a15d..89ac522d237 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -80,7 +80,7 @@ async fn payments_create_core() { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), - card_holder_name: "Arun Raj".to_string().into(), + card_holder_name: Some(masking::Secret::new("Arun Raj".to_string())), card_cvc: "123".to_string().into(), card_issuer: None, card_network: None, @@ -263,7 +263,7 @@ async fn payments_create_core_adyen_no_redirect() { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), - card_holder_name: "JohnDoe".to_string().into(), + card_holder_name: Some(masking::Secret::new("JohnDoe".to_string())), card_cvc: "737".to_string().into(), bank_code: None, card_issuer: None, diff --git a/crates/router/tests/refunds.rs b/crates/router/tests/refunds.rs index 6b9dfd5ed4a..8d6a158cdff 100644 --- a/crates/router/tests/refunds.rs +++ b/crates/router/tests/refunds.rs @@ -19,7 +19,7 @@ async fn refund_create_fail_stripe() { let payment_id = format!("test_{}", uuid::Uuid::new_v4()); let refund: serde_json::Value = user_client.create_refund(&app, &payment_id, 10).await; - assert_eq!(refund["error"]["message"], "Access forbidden, invalid API key was used. Please create your new API key from the Dashboard Settings section."); + assert_eq!(refund.get("error").unwrap().get("message").unwrap(), "Access forbidden, invalid API key was used. Please create your new API key from the Dashboard Settings section."); } #[actix_web::test] @@ -33,7 +33,7 @@ async fn refund_create_fail_adyen() { let payment_id = format!("test_{}", uuid::Uuid::new_v4()); let refund: serde_json::Value = user_client.create_refund(&app, &payment_id, 10).await; - assert_eq!(refund["error"]["message"], "Access forbidden, invalid API key was used. Please create your new API key from the Dashboard Settings section."); + assert_eq!(refund.get("error").unwrap().get("message").unwrap(), "Access forbidden, invalid API key was used. Please create your new API key from the Dashboard Settings section."); } #[actix_web::test] diff --git a/crates/router_derive/Cargo.toml b/crates/router_derive/Cargo.toml index 6f598e0f050..07e95b9232c 100644 --- a/crates/router_derive/Cargo.toml +++ b/crates/router_derive/Cargo.toml @@ -12,7 +12,7 @@ proc-macro = true doctest = false [dependencies] -indexmap = "2.0.0" +indexmap = "2.1.0" proc-macro2 = "1.0.56" quote = "1.0.26" syn = { version = "2.0.5", features = ["full", "extra-traits"] } # the full feature does not seem to encompass all the features @@ -20,6 +20,6 @@ strum = { version = "0.24.1", features = ["derive"] } [dev-dependencies] diesel = { version = "2.1.0", features = ["postgres"] } -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index 266baf0e386..ae82a0c094d 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -16,9 +16,9 @@ once_cell = "1.18.0" opentelemetry = { version = "0.19.0", features = ["rt-tokio-current-thread", "metrics"] } opentelemetry-otlp = { version = "0.12.0", features = ["metrics"] } rustc-hash = "1.1" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" -serde_path_to_error = "0.1.11" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +serde_path_to_error = "0.1.14" strum = { version = "0.24.1", features = ["derive"] } time = { version = "0.3.21", default-features = false, features = ["formatting"] } tokio = { version = "1.28.2" } @@ -43,4 +43,4 @@ actix_web = ["tracing-actix-web"] log_custom_entries_to_extra = [] log_extra_implicit_fields = [] log_active_span_json = [] -payouts = [] +payouts = [] \ No newline at end of file diff --git a/crates/router_env/src/env.rs b/crates/router_env/src/env.rs index e57c38e7e4a..644f9b50a12 100644 --- a/crates/router_env/src/env.rs +++ b/crates/router_env/src/env.rs @@ -167,3 +167,13 @@ macro_rules! profile { env!("CARGO_PROFILE") }; } + +/// The latest git tag. If tags are not present in the repository, the short commit hash is used +/// instead. Refer to the [`git describe`](https://git-scm.com/docs/git-describe) documentation for +/// more details. +#[macro_export] +macro_rules! git_tag { + () => { + env!("VERGEN_GIT_DESCRIBE") + }; +} diff --git a/crates/router_env/src/lib.rs b/crates/router_env/src/lib.rs index 3c7ba8b93df..9139b5eed41 100644 --- a/crates/router_env/src/lib.rs +++ b/crates/router_env/src/lib.rs @@ -52,6 +52,8 @@ pub enum AnalyticsFlow { GenerateRefundReport, GetApiEventMetrics, GetApiEventFilters, + GetConnectorEvents, + GetOutgoingWebhookEvents, } impl FlowMetric for AnalyticsFlow {} diff --git a/crates/router_env/src/logger/storage.rs b/crates/router_env/src/logger/storage.rs index 04f00f71cb3..51e701213b9 100644 --- a/crates/router_env/src/logger/storage.rs +++ b/crates/router_env/src/logger/storage.rs @@ -77,8 +77,12 @@ impl Visit for Storage<'_> { // Skip fields which are already handled name if name.starts_with("log.") => (), name if name.starts_with("r#") => { - self.values - .insert(&name[2..], serde_json::Value::from(format!("{value:?}"))); + self.values.insert( + #[allow(clippy::expect_used)] + name.get(2..) + .expect("field name must have a minimum of two characters"), + serde_json::Value::from(format!("{value:?}")), + ); } name => { self.values @@ -88,12 +92,12 @@ impl Visit for Storage<'_> { } } -#[allow(clippy::expect_used)] impl tracing_subscriber::registry::LookupSpan<'a>> Layer for StorageSubscription { /// On new span. fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + #[allow(clippy::expect_used)] let span = ctx.span(id).expect("No span"); let mut visitor = if let Some(parent_span) = span.parent() { @@ -113,8 +117,10 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer /// On additional key value pairs store it. fn on_record(&self, span: &Id, values: &Record<'_>, ctx: Context<'_, S>) { + #[allow(clippy::expect_used)] let span = ctx.span(span).expect("No span"); let mut extensions = span.extensions_mut(); + #[allow(clippy::expect_used)] let visitor = extensions .get_mut::>() .expect("The span does not have storage"); @@ -123,6 +129,7 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer /// On enter store time. fn on_enter(&self, span: &Id, ctx: Context<'_, S>) { + #[allow(clippy::expect_used)] let span = ctx.span(span).expect("No span"); let mut extensions = span.extensions_mut(); if extensions.get_mut::().is_none() { @@ -132,6 +139,7 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer /// On close create an entry about how long did it take. fn on_close(&self, span: Id, ctx: Context<'_, S>) { + #[allow(clippy::expect_used)] let span = ctx.span(&span).expect("No span"); let elapsed_milliseconds = { @@ -143,6 +151,7 @@ impl tracing_subscriber::registry::LookupSpan<'a>> Layer }; let mut extensions_mut = span.extensions_mut(); + #[allow(clippy::expect_used)] let visitor = extensions_mut .get_mut::>() .expect("No visitor in extensions"); diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 6344c21f5f8..0d6636e567d 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -165,6 +165,14 @@ pub enum Flow { RefundsList, // Retrieve forex flow. RetrieveForexFlow, + /// Toggles recon service for a merchant. + ReconMerchantUpdate, + /// Recon token request flow. + ReconTokenRequest, + /// Initial request for recon service. + ReconServiceRequest, + /// Recon token verification flow + ReconVerifyToken, /// Routing create flow, RoutingCreateConfig, /// Routing link config @@ -185,6 +193,12 @@ pub enum Flow { RoutingUpdateDefaultConfig, /// Routing delete config RoutingDeleteConfig, + /// Add record to blocklist + AddToBlocklist, + /// Delete record from blocklist + DeleteFromBlocklist, + /// List entries from blocklist + ListBlocklist, /// Incoming Webhook Receive IncomingWebhookReceive, /// Validate payment method flow @@ -311,8 +325,12 @@ pub enum Flow { GetActionUrl, /// Sync connector onboarding status SyncOnboardingStatus, + /// Reset tracking id + ResetTrackingId, /// Verify email Token VerifyEmail, + /// Send verify email + VerifyEmailRequest, } /// diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index 5e8674ab381..40f7ff7b947 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -15,8 +15,8 @@ error-stack = "0.3.1" futures = "0.3.28" once_cell = "1.18.0" rand = "0.8.5" -serde = "1.0.163" -serde_json = "1.0.96" +serde = "1.0.193" +serde_json = "1.0.108" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.40" time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/scheduler/src/consumer.rs b/crates/scheduler/src/consumer.rs index 08899552704..ccc943afba3 100644 --- a/crates/scheduler/src/consumer.rs +++ b/crates/scheduler/src/consumer.rs @@ -40,9 +40,15 @@ pub async fn start_consumer( use rand::distributions::{Distribution, Uniform}; let mut rng = rand::thread_rng(); + + // TODO: this can be removed once rand-0.9 is released + // reference - https://github.com/rust-random/rand/issues/1326#issuecomment-1635331942 + #[allow(unknown_lints)] + #[allow(clippy::unnecessary_fallible_conversions)] let timeout = Uniform::try_from(0..=settings.loop_interval) .into_report() .change_context(errors::ProcessTrackerError::ConfigurationError)?; + tokio::time::sleep(Duration::from_millis(timeout.sample(&mut rng))).await; let mut interval = tokio::time::interval(Duration::from_millis(settings.loop_interval)); @@ -61,7 +67,7 @@ pub async fn start_consumer( let handle = signal.handle(); let task_handle = tokio::spawn(common_utils::signals::signal_handler(signal, tx)); - loop { + 'consumer: loop { match rx.try_recv() { Err(mpsc::error::TryRecvError::Empty) => { interval.tick().await; @@ -71,7 +77,7 @@ pub async fn start_consumer( continue; } - tokio::task::spawn(pt_utils::consumer_operation_handler( + pt_utils::consumer_operation_handler( state.clone(), settings.clone(), |err| { @@ -79,19 +85,23 @@ pub async fn start_consumer( }, sync::Arc::clone(&consumer_operation_counter), workflow_selector, - )); + ) + .await; } Ok(()) | Err(mpsc::error::TryRecvError::Disconnected) => { logger::debug!("Awaiting shutdown!"); rx.close(); - shutdown_interval.tick().await; - let active_tasks = consumer_operation_counter.load(atomic::Ordering::Acquire); - match active_tasks { - 0 => { - logger::info!("Terminating consumer"); - break; + loop { + shutdown_interval.tick().await; + let active_tasks = consumer_operation_counter.load(atomic::Ordering::Acquire); + logger::error!("{}", active_tasks); + match active_tasks { + 0 => { + logger::info!("Terminating consumer"); + break 'consumer; + } + _ => continue, } - _ => continue, } } } @@ -204,6 +214,7 @@ where T: SchedulerAppState, { tracing::Span::current().record("workflow_id", Uuid::new_v4().to_string()); + logger::info!("{:?}", process.name.as_ref()); let res = workflow_selector .trigger_workflow(&state.clone(), process.clone()) .await; diff --git a/crates/scheduler/src/producer.rs b/crates/scheduler/src/producer.rs index 52510e1842e..bcf37cdf6f2 100644 --- a/crates/scheduler/src/producer.rs +++ b/crates/scheduler/src/producer.rs @@ -30,9 +30,15 @@ where use rand::distributions::{Distribution, Uniform}; let mut rng = rand::thread_rng(); + + // TODO: this can be removed once rand-0.9 is released + // reference - https://github.com/rust-random/rand/issues/1326#issuecomment-1635331942 + #[allow(unknown_lints)] + #[allow(clippy::unnecessary_fallible_conversions)] let timeout = Uniform::try_from(0..=scheduler_settings.loop_interval) .into_report() .change_context(errors::ProcessTrackerError::ConfigurationError)?; + tokio::time::sleep(Duration::from_millis(timeout.sample(&mut rng))).await; let mut interval = tokio::time::interval(std::time::Duration::from_millis( diff --git a/crates/scheduler/src/utils.rs b/crates/scheduler/src/utils.rs index 53f14bd1fb9..32fd97fca33 100644 --- a/crates/scheduler/src/utils.rs +++ b/crates/scheduler/src/utils.rs @@ -252,7 +252,7 @@ pub async fn consumer_operation_handler( E: FnOnce(error_stack::Report), T: SchedulerAppState, { - consumer_operation_counter.fetch_add(1, atomic::Ordering::Release); + consumer_operation_counter.fetch_add(1, atomic::Ordering::SeqCst); let start_time = std_time::Instant::now(); match consumer::consumer_operations(&state, &settings, workflow_selector).await { @@ -263,7 +263,7 @@ pub async fn consumer_operation_handler( let duration = end_time.saturating_duration_since(start_time).as_secs_f64(); logger::debug!("Time taken to execute consumer_operation: {}s", duration); - let current_count = consumer_operation_counter.fetch_sub(1, atomic::Ordering::Release); + let current_count = consumer_operation_counter.fetch_sub(1, atomic::Ordering::SeqCst); logger::info!("Current tasks being executed: {}", current_count); } diff --git a/crates/storage_impl/Cargo.toml b/crates/storage_impl/Cargo.toml index 77589cc7d78..0155980d9f7 100644 --- a/crates/storage_impl/Cargo.toml +++ b/crates/storage_impl/Cargo.toml @@ -38,10 +38,10 @@ error-stack = "0.3.1" futures = "0.3.28" http = "0.2.9" mime = "0.3.17" -moka = { version = "0.11.3", features = ["future"] } +moka = { version = "0.12", features = ["future"] } once_cell = "1.18.0" ring = "0.16.20" -serde = { version = "1.0.185", features = ["derive"] } -serde_json = "1.0.105" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" thiserror = "1.0.40" tokio = { version = "1.28.2", features = ["rt-multi-thread"] } diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index f0cbebf78c5..ac3a04e85b2 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -55,6 +55,8 @@ pub enum StorageError { SerializationFailed, #[error("MockDb error")] MockDbError, + #[error("Kafka error")] + KafkaError, #[error("Customer with this id is Redacted")] CustomerRedacted, #[error("Deserialization failure")] @@ -103,6 +105,7 @@ impl Into for &StorageError { StorageError::KVError => DataStorageError::KVError, StorageError::SerializationFailed => DataStorageError::SerializationFailed, StorageError::MockDbError => DataStorageError::MockDbError, + StorageError::KafkaError => DataStorageError::KafkaError, StorageError::CustomerRedacted => DataStorageError::CustomerRedacted, StorageError::DeserializationFailed => DataStorageError::DeserializationFailed, StorageError::EncryptionError => DataStorageError::EncryptionError, @@ -376,3 +379,53 @@ pub enum ConnectorError { #[error("Missing 3DS redirection payload: {field_name}")] MissingConnectorRedirectionPayload { field_name: &'static str }, } + +#[derive(Debug, thiserror::Error)] +pub enum HealthCheckDBError { + #[error("Error while connecting to database")] + DBError, + #[error("Error while writing to database")] + DBWriteError, + #[error("Error while reading element in the database")] + DBReadError, + #[error("Error while deleting element in the database")] + DBDeleteError, + #[error("Unpredictable error occurred")] + UnknownError, + #[error("Error in database transaction")] + TransactionError, +} + +impl From for HealthCheckDBError { + fn from(error: diesel::result::Error) -> Self { + match error { + diesel::result::Error::DatabaseError(_, _) => Self::DBError, + + diesel::result::Error::RollbackErrorOnCommit { .. } + | diesel::result::Error::RollbackTransaction + | diesel::result::Error::AlreadyInTransaction + | diesel::result::Error::NotInTransaction + | diesel::result::Error::BrokenTransactionManager => Self::TransactionError, + + _ => Self::UnknownError, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum HealthCheckRedisError { + #[error("Failed to establish Redis connection")] + RedisConnectionError, + #[error("Failed to set key value in Redis")] + SetFailed, + #[error("Failed to get key value in Redis")] + GetFailed, + #[error("Failed to delete key value in Redis")] + DeleteFailed, +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum HealthCheckLockerError { + #[error("Failed to establish Locker connection")] + FailedToCallLocker, +} diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index e22d39ce70c..a6ba763bd91 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -43,6 +43,7 @@ pub struct MockDb { pub organizations: Arc>>, pub users: Arc>>, pub user_roles: Arc>>, + pub authorizations: Arc>>, pub dashboard_metadata: Arc>>, } @@ -79,6 +80,7 @@ impl MockDb { organizations: Default::default(), users: Default::default(), user_roles: Default::default(), + authorizations: Default::default(), dashboard_metadata: Default::default(), }) } diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 6137b444f96..24863ddc568 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -97,7 +97,7 @@ impl PaymentAttemptInterface for MockDb { #[allow(clippy::as_conversions)] let id = payment_attempts.len() as i32; let time = common_utils::date_time::now(); - + let payment_attempt = payment_attempt.populate_derived_fields(); let payment_attempt = PaymentAttempt { id, payment_id: payment_attempt.payment_id, @@ -105,6 +105,7 @@ impl PaymentAttemptInterface for MockDb { attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, amount: payment_attempt.amount, + net_amount: payment_attempt.net_amount, currency: payment_attempt.currency, save_to_locker: payment_attempt.save_to_locker, connector: payment_attempt.connector, diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 1da3df0bdef..3f892ed9fa7 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -109,6 +109,8 @@ impl PaymentIntentInterface for MockDb { request_incremental_authorization: new.request_incremental_authorization, incremental_authorization_allowed: new.incremental_authorization_allowed, authorization_count: new.authorization_count, + fingerprint_id: new.fingerprint_id, + session_expiry: new.session_expiry, }; payment_intents.push(payment_intent.clone()); Ok(payment_intent) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 425cdd216fe..f8f752c6bc8 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -331,6 +331,7 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { + let payment_attempt = payment_attempt.populate_derived_fields(); let key = format!( "mid_{}_pid_{}", payment_attempt.merchant_id, payment_attempt.payment_id @@ -343,6 +344,7 @@ impl PaymentAttemptInterface for KVRouterStore { attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, amount: payment_attempt.amount, + net_amount: payment_attempt.net_amount, currency: payment_attempt.currency, save_to_locker: payment_attempt.save_to_locker, connector: payment_attempt.connector.clone(), @@ -1035,6 +1037,7 @@ impl DataModelExt for PaymentAttempt { attempt_id: self.attempt_id, status: self.status, amount: self.amount, + net_amount: Some(self.net_amount), currency: self.currency, save_to_locker: self.save_to_locker, connector: self.connector, @@ -1081,6 +1084,7 @@ impl DataModelExt for PaymentAttempt { fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { + net_amount: storage_model.get_or_calculate_net_amount(), id: storage_model.id, payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, @@ -1139,6 +1143,7 @@ impl DataModelExt for PaymentAttemptNew { fn to_storage_model(self) -> Self::StorageModel { DieselPaymentAttemptNew { + net_amount: Some(self.net_amount), payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, @@ -1189,6 +1194,7 @@ impl DataModelExt for PaymentAttemptNew { fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { + net_amount: storage_model.get_or_calculate_net_amount(), payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, attempt_id: storage_model.attempt_id, diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 61229ca890c..8d20dfe0f32 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -101,6 +101,8 @@ impl PaymentIntentInterface for KVRouterStore { request_incremental_authorization: new.request_incremental_authorization, incremental_authorization_allowed: new.incremental_authorization_allowed, authorization_count: new.authorization_count, + fingerprint_id: new.fingerprint_id.clone(), + session_expiry: new.session_expiry, }; let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { @@ -768,6 +770,8 @@ impl DataModelExt for PaymentIntentNew { request_incremental_authorization: self.request_incremental_authorization, incremental_authorization_allowed: self.incremental_authorization_allowed, authorization_count: self.authorization_count, + fingerprint_id: self.fingerprint_id, + session_expiry: self.session_expiry, } } @@ -811,6 +815,8 @@ impl DataModelExt for PaymentIntentNew { request_incremental_authorization: storage_model.request_incremental_authorization, incremental_authorization_allowed: storage_model.incremental_authorization_allowed, authorization_count: storage_model.authorization_count, + fingerprint_id: storage_model.fingerprint_id, + session_expiry: storage_model.session_expiry, } } } @@ -859,6 +865,8 @@ impl DataModelExt for PaymentIntent { request_incremental_authorization: self.request_incremental_authorization, incremental_authorization_allowed: self.incremental_authorization_allowed, authorization_count: self.authorization_count, + fingerprint_id: self.fingerprint_id, + session_expiry: self.session_expiry, } } @@ -903,6 +911,8 @@ impl DataModelExt for PaymentIntent { request_incremental_authorization: storage_model.request_incremental_authorization, incremental_authorization_allowed: storage_model.incremental_authorization_allowed, authorization_count: storage_model.authorization_count, + fingerprint_id: storage_model.fingerprint_id, + session_expiry: storage_model.session_expiry, } } } @@ -985,6 +995,8 @@ impl DataModelExt for PaymentIntentUpdate { metadata, payment_confirm_source, updated_by, + fingerprint_id, + session_expiry, } => DieselPaymentIntentUpdate::Update { amount, currency, @@ -1003,6 +1015,8 @@ impl DataModelExt for PaymentIntentUpdate { metadata, payment_confirm_source, updated_by, + fingerprint_id, + session_expiry, }, Self::PaymentAttemptAndAttemptCountUpdate { active_attempt_id, diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index cd606662762..a960261f867 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -109,7 +109,6 @@ impl Cache { /// `max_capacity`: Max size in MB's that the cache can hold pub fn new(time_to_live: u64, time_to_idle: u64, max_capacity: Option) -> Self { let mut cache_builder = MokaCache::builder() - .eviction_listener_with_queued_delivery_mode(|_, _, _| {}) .time_to_live(std::time::Duration::from_secs(time_to_live)) .time_to_idle(std::time::Duration::from_secs(time_to_idle)); @@ -126,8 +125,8 @@ impl Cache { self.insert(key, Arc::new(val)).await; } - pub fn get_val(&self, key: &str) -> Option { - let val = self.get(key)?; + pub async fn get_val(&self, key: &str) -> Option { + let val = self.get(key).await?; (*val).as_any().downcast_ref::().cloned() } @@ -188,7 +187,7 @@ where F: FnOnce() -> Fut + Send, Fut: futures::Future> + Send, { - let cache_val = cache.get_val::(key); + let cache_val = cache.get_val::(key).await; if let Some(val) = cache_val { Ok(val) } else { @@ -266,14 +265,17 @@ mod cache_tests { async fn construct_and_get_cache() { let cache = Cache::new(1800, 1800, None); cache.push("key".to_string(), "val".to_string()).await; - assert_eq!(cache.get_val::("key"), Some(String::from("val"))); + assert_eq!( + cache.get_val::("key").await, + Some(String::from("val")) + ); } #[tokio::test] async fn eviction_on_size_test() { let cache = Cache::new(2, 2, Some(0)); cache.push("key".to_string(), "val".to_string()).await; - assert_eq!(cache.get_val::("key"), None); + assert_eq!(cache.get_val::("key").await, None); } #[tokio::test] @@ -283,7 +285,7 @@ mod cache_tests { cache.remove("key").await; - assert_eq!(cache.get_val::("key"), None); + assert_eq!(cache.get_val::("key").await, None); } #[tokio::test] @@ -291,6 +293,6 @@ mod cache_tests { let cache = Cache::new(2, 2, None); cache.push("key".to_string(), "val".to_string()).await; tokio::time::sleep(std::time::Duration::from_secs(3)).await; - assert_eq!(cache.get_val::("key"), None); + assert_eq!(cache.get_val::("key").await, None); } } diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index 957a51171da..a95e2e3921b 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml @@ -18,8 +18,8 @@ base64 = "0.21.2" clap = { version = "4.3.2", default-features = false, features = ["std", "derive", "help", "usage"] } rand = "0.8.5" reqwest = { version = "0.11.18", features = ["native-tls"] } -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" serde_urlencoded = "0.7.1" serial_test = "2.0.0" thirtyfour = "0.31.0" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 9562972c126..d95d6ed94f1 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -48,6 +48,7 @@ pub struct ConnectorAuthentication { pub payme: Option, pub paypal: Option, pub payu: Option, + pub placetopay: Option, pub powertranz: Option, pub prophetpay: Option, pub rapyd: Option, diff --git a/crates/test_utils/tests/connectors/selenium.rs b/crates/test_utils/tests/connectors/selenium.rs index 865cd950f76..303d4cd7ccb 100644 --- a/crates/test_utils/tests/connectors/selenium.rs +++ b/crates/test_utils/tests/connectors/selenium.rs @@ -397,7 +397,7 @@ pub trait SeleniumTest { ) -> Result<(), WebDriverError> { let config = self.get_configs().automation_configs.unwrap(); if config.run_minimum_steps.unwrap() { - self.complete_actions(&web_driver, actions[..3].to_vec()) + self.complete_actions(&web_driver, actions.get(..3).unwrap().to_vec()) .await } else { self.complete_actions(&web_driver, actions).await @@ -538,7 +538,7 @@ pub trait SeleniumTest { let response = client.get(outgoing_webhook_url).send().await.unwrap(); // get events from outgoing webhook endpoint let body_text = response.text().await.unwrap(); let data: WebhookResponse = serde_json::from_str(&body_text).unwrap(); - let last_three_events = &data.data[data.data.len().saturating_sub(3)..]; // Get the last three elements if available + let last_three_events = data.data.get(data.data.len().saturating_sub(3)..).unwrap(); // Get the last three elements if available for last_event in last_three_events { let last_event_body = &last_event.step.request.body; let decoded_bytes = base64::engine::general_purpose::STANDARD //decode the encoded outgoing webhook event @@ -762,7 +762,7 @@ macro_rules! function { std::any::type_name::() } let name = type_name_of(f); - &name[..name.len() - 3] + &name.get(..name.len() - 3).unwrap() }}; } @@ -812,8 +812,14 @@ pub fn should_ignore_test(name: &str) -> bool { let tests_to_ignore: HashSet = serde_json::from_value(conf).unwrap_or_else(|_| HashSet::new()); let modules: Vec<_> = name.split("::").collect(); - let file_match = format!("{}::*", <&str>::clone(&modules[1])); - let module_name = modules[1..3].join("::"); + let file_match = format!( + "{}::*", + <&str>::clone(modules.get(1).expect("Error obtaining module path segment")) + ); + let module_name = modules + .get(1..3) + .expect("Error obtaining module path segment") + .join("::"); // Ignore if it matches patterns like nuvei_ui::*, nuvei_ui::should_make_nuvei_eps_payment_test tests_to_ignore.contains(&file_match) || tests_to_ignore.contains(&module_name) } diff --git a/crates/test_utils/tests/sample_auth.toml b/crates/test_utils/tests/sample_auth.toml index 0ae7c40d42d..08b24817c24 100644 --- a/crates/test_utils/tests/sample_auth.toml +++ b/crates/test_utils/tests/sample_auth.toml @@ -108,7 +108,7 @@ api_key = "API Key" [iatapay] key1 = "key1" api_key = "api_key" -api_secret = "secrect" +api_secret = "secret" [dummyconnector] api_key = "API Key" diff --git a/docker-compose-development.yml b/docker-compose-development.yml index 500f397cfa3..5a3eca4cdf3 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -28,8 +28,6 @@ services: redis-standalone: image: redis:7 - labels: - - redis networks: - router_net ports: @@ -159,8 +157,6 @@ services: - clustered_redis volumes: - ./config/redis.conf:/usr/local/etc/redis/redis.conf - labels: - - redis networks: - router_net ports: diff --git a/docker-compose.yml b/docker-compose.yml index f51a47aee94..9f8e7bb4efb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,8 +24,6 @@ services: redis-standalone: image: redis:7 - labels: - - redis networks: - router_net ports: @@ -133,8 +131,6 @@ services: - clustered_redis volumes: - ./config/redis.conf:/usr/local/etc/redis/redis.conf - labels: - - redis networks: - router_net ports: diff --git a/docker/wasm-build.Dockerfile b/docker/wasm-build.Dockerfile new file mode 100644 index 00000000000..2ebdcec217e --- /dev/null +++ b/docker/wasm-build.Dockerfile @@ -0,0 +1,25 @@ +FROM rust:latest as builder + +ARG RUN_ENV=sandbox +ARG EXTRA_FEATURES="" + +RUN apt-get update \ + && apt-get install -y libssl-dev pkg-config + +ENV CARGO_INCREMENTAL=0 +# Allow more retries for network requests in cargo (downloading crates) and +# rustup (installing toolchains). This should help to reduce flaky CI failures +# from transient network timeouts or other issues. +ENV CARGO_NET_RETRY=10 +ENV RUSTUP_MAX_RETRIES=10 +# Don't emit giant backtraces in the CI logs. +ENV RUST_BACKTRACE="short" +ENV env=$env +COPY . . +RUN echo env +RUN cargo install wasm-pack +RUN wasm-pack build --target web --out-dir /tmp/wasm --out-name euclid crates/euclid_wasm -- --features ${RUN_ENV},${EXTRA_FEATURES} + +FROM scratch + +COPY --from=builder /tmp/wasm /tmp diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index f4070beb943..358a591a667 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -51,12 +51,6 @@ max_attempts = 10 max_age = 365 [jwekey] -locker_key_identifier1 = "" -locker_key_identifier2 = "" -locker_encryption_key1 = "" -locker_encryption_key2 = "" -locker_decryption_key1 = "" -locker_decryption_key2 = "" vault_encryption_key = "" rust_locker_encryption_key = "" vault_private_key = "" @@ -111,9 +105,11 @@ payeezy.base_url = "https://api-cert.payeezy.com/" payme.base_url = "https://sandbox.payme.io/" paypal.base_url = "https://api-m.sandbox.paypal.com/" payu.base_url = "https://secure.snd.payu.com/" +placetopay.base_url = "https://test.placetopay.com/rest/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" @@ -173,6 +169,7 @@ cards = [ "payme", "paypal", "payu", + "placetopay", "powertranz", "prophetpay", "shift4", @@ -203,7 +200,7 @@ red_pagos = { country = "UY", currency = "UYU" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } -checkout = { long_lived_token = false, payment_method = "wallet" } +checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } mollie = {long_lived_token = false, payment_method = "card"} braintree = { long_lived_token = false, payment_method = "card" } gocardless = {long_lived_token = true, payment_method = "bank_debit"} @@ -248,6 +245,7 @@ card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalp bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} +bank_redirect.ideal = {connector_list = "stripe,adyen"} [analytics] source = "sqlx" diff --git a/migrations/2023-11-24-112541_add_payment_config_business_profile/down.sql b/migrations/2023-11-24-112541_add_payment_config_business_profile/down.sql new file mode 100644 index 00000000000..16705c2bf56 --- /dev/null +++ b/migrations/2023-11-24-112541_add_payment_config_business_profile/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile DROP COLUMN IF EXISTS payment_link_config; \ No newline at end of file diff --git a/migrations/2023-11-24-112541_add_payment_config_business_profile/up.sql b/migrations/2023-11-24-112541_add_payment_config_business_profile/up.sql new file mode 100644 index 00000000000..40e65c149f2 --- /dev/null +++ b/migrations/2023-11-24-112541_add_payment_config_business_profile/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE business_profile +ADD COLUMN IF NOT EXISTS payment_link_config JSONB DEFAULT NULL; diff --git a/migrations/2023-11-24-115538_add_profile_id_payment_link/down.sql b/migrations/2023-11-24-115538_add_profile_id_payment_link/down.sql new file mode 100644 index 00000000000..4fa87f5f931 --- /dev/null +++ b/migrations/2023-11-24-115538_add_profile_id_payment_link/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_link DROP COLUMN profile_id; \ No newline at end of file diff --git a/migrations/2023-11-24-115538_add_profile_id_payment_link/up.sql b/migrations/2023-11-24-115538_add_profile_id_payment_link/up.sql new file mode 100644 index 00000000000..207fdc8817e --- /dev/null +++ b/migrations/2023-11-24-115538_add_profile_id_payment_link/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_link ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64) DEFAULT NULL; diff --git a/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/down.sql b/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/down.sql new file mode 100644 index 00000000000..6af3e1e7f3d --- /dev/null +++ b/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS session_expiry; diff --git a/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/up.sql b/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/up.sql new file mode 100644 index 00000000000..e6ad0a728d4 --- /dev/null +++ b/migrations/2023-12-06-112810_add_intent_fullfilment_time_payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS session_expiry TIMESTAMP DEFAULT NULL; diff --git a/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/down.sql b/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/down.sql new file mode 100644 index 00000000000..1e7311bedbc --- /dev/null +++ b/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent ALTER COLUMN request_incremental_authorization SET NOT NULL; \ No newline at end of file diff --git a/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/up.sql b/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/up.sql new file mode 100644 index 00000000000..af2b897aa24 --- /dev/null +++ b/migrations/2023-12-07-075240_make-request-incremental-auth-optional-intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ALTER COLUMN request_incremental_authorization DROP NOT NULL; \ No newline at end of file diff --git a/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql b/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql new file mode 100644 index 00000000000..74c450622a7 --- /dev/null +++ b/migrations/2023-12-11-075542_create_pm_fingerprint_table/down.sql @@ -0,0 +1,5 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE blocklist_fingerprint; + +DROP TYPE "BlocklistDataKind"; diff --git a/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql b/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql new file mode 100644 index 00000000000..417d779200f --- /dev/null +++ b/migrations/2023-12-11-075542_create_pm_fingerprint_table/up.sql @@ -0,0 +1,19 @@ +-- Your SQL goes here + +CREATE TYPE "BlocklistDataKind" AS ENUM ( + 'payment_method', + 'card_bin', + 'extended_card_bin' +); + +CREATE TABLE blocklist_fingerprint ( + id SERIAL PRIMARY KEY, + merchant_id VARCHAR(64) NOT NULL, + fingerprint_id VARCHAR(64) NOT NULL, + data_kind "BlocklistDataKind" NOT NULL, + encrypted_fingerprint TEXT NOT NULL, + created_at TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX blocklist_fingerprint_merchant_id_fingerprint_id_index +ON blocklist_fingerprint (merchant_id, fingerprint_id); diff --git a/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql b/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql new file mode 100644 index 00000000000..cd7d412aad9 --- /dev/null +++ b/migrations/2023-12-12-112941_create_pm_blocklist_table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE blocklist; diff --git a/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql b/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql new file mode 100644 index 00000000000..6d921dd78c3 --- /dev/null +++ b/migrations/2023-12-12-112941_create_pm_blocklist_table/up.sql @@ -0,0 +1,13 @@ +-- Your SQL goes here + +CREATE TABLE blocklist ( + id SERIAL PRIMARY KEY, + merchant_id VARCHAR(64) NOT NULL, + fingerprint_id VARCHAR(64) NOT NULL, + data_kind "BlocklistDataKind" NOT NULL, + metadata JSONB, + created_at TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX blocklist_unique_fingerprint_id_index ON blocklist (merchant_id, fingerprint_id); +CREATE INDEX blocklist_merchant_id_data_kind_created_at_index ON blocklist (merchant_id, data_kind, created_at DESC); diff --git a/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql new file mode 100644 index 00000000000..46b871b6ee4 --- /dev/null +++ b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS fingerprint_id; diff --git a/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql new file mode 100644 index 00000000000..831fb7b6ffc --- /dev/null +++ b/migrations/2023-12-12-113330_add_fingerprint_id_in_payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS fingerprint_id VARCHAR(64); diff --git a/migrations/2023-12-14-060824_user_roles_user_status_column/down.sql b/migrations/2023-12-14-060824_user_roles_user_status_column/down.sql new file mode 100644 index 00000000000..a246675a7d0 --- /dev/null +++ b/migrations/2023-12-14-060824_user_roles_user_status_column/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE user_roles RENAME COLUMN last_modified TO last_modified_at; +ALTER TABLE user_roles ALTER COLUMN status TYPE VARCHAR(64) USING (status::text); +DROP TYPE IF EXISTS "UserStatus"; diff --git a/migrations/2023-12-14-060824_user_roles_user_status_column/up.sql b/migrations/2023-12-14-060824_user_roles_user_status_column/up.sql new file mode 100644 index 00000000000..4d0245fcc3a --- /dev/null +++ b/migrations/2023-12-14-060824_user_roles_user_status_column/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE user_roles RENAME COLUMN last_modified_at TO last_modified; +CREATE TYPE "UserStatus" AS ENUM ('active', 'invitation_sent'); +ALTER TABLE user_roles ALTER COLUMN status TYPE "UserStatus" USING (status::"UserStatus"); diff --git a/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/down.sql b/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/down.sql new file mode 100644 index 00000000000..bd2a8e1060a --- /dev/null +++ b/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE dashboard_metadata ALTER COLUMN data_key TYPE VARCHAR(64); +DROP TYPE IF EXISTS "DashboardMetadata"; diff --git a/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/up.sql b/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/up.sql new file mode 100644 index 00000000000..653bee36866 --- /dev/null +++ b/migrations/2023-12-14-101348_alter_dashboard_metadata_key_type/up.sql @@ -0,0 +1,25 @@ +-- Your SQL goes here +CREATE TYPE "DashboardMetadata" AS ENUM ( + 'production_agreement', + 'setup_processor', + 'configure_endpoint', + 'setup_complete', + 'first_processor_connected', + 'second_processor_connected', + 'configured_routing', + 'test_payment', + 'integration_method', + 'stripe_connected', + 'paypal_connected', + 'sp_routing_configured', + 'sp_test_payment', + 'download_woocom', + 'configure_woocom', + 'setup_woocom_webhook', + 'is_multiple_configuration', + 'configuration_type', + 'feedback', + 'prod_intent' +); + +ALTER TABLE dashboard_metadata ALTER COLUMN data_key TYPE "DashboardMetadata" USING (data_key::"DashboardMetadata"); \ No newline at end of file diff --git a/migrations/2023-12-15-062816__net_amount_in_payment_attempt/down.sql b/migrations/2023-12-15-062816__net_amount_in_payment_attempt/down.sql new file mode 100644 index 00000000000..93cd6341a9b --- /dev/null +++ b/migrations/2023-12-15-062816__net_amount_in_payment_attempt/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS net_amount; \ No newline at end of file diff --git a/migrations/2023-12-15-062816__net_amount_in_payment_attempt/up.sql b/migrations/2023-12-15-062816__net_amount_in_payment_attempt/up.sql new file mode 100644 index 00000000000..fa86b097ef2 --- /dev/null +++ b/migrations/2023-12-15-062816__net_amount_in_payment_attempt/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS net_amount BIGINT; +-- Backfill +UPDATE payment_attempt pa +SET net_amount = pa.amount + COALESCE(pa.surcharge_amount, 0) + COALESCE(pa.tax_amount, 0); diff --git a/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql b/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql new file mode 100644 index 00000000000..d2363f547a5 --- /dev/null +++ b/migrations/2023-12-18-062613_create_blocklist_lookup_table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE blocklist_lookup; diff --git a/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql b/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql new file mode 100644 index 00000000000..8af3e209fc6 --- /dev/null +++ b/migrations/2023-12-18-062613_create_blocklist_lookup_table/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +CREATE TABLE blocklist_lookup ( + id SERIAL PRIMARY KEY, + merchant_id VARCHAR(64) NOT NULL, + fingerprint TEXT NOT NULL +); + +CREATE UNIQUE INDEX blocklist_lookup_merchant_id_fingerprint_index ON blocklist_lookup (merchant_id, fingerprint); diff --git a/migrations/2023-12-27-104559_business_profile_add_session_expiry/down.sql b/migrations/2023-12-27-104559_business_profile_add_session_expiry/down.sql new file mode 100644 index 00000000000..cdc7fe5b81c --- /dev/null +++ b/migrations/2023-12-27-104559_business_profile_add_session_expiry/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile DROP COLUMN IF EXISTS session_expiry; \ No newline at end of file diff --git a/migrations/2023-12-27-104559_business_profile_add_session_expiry/up.sql b/migrations/2023-12-27-104559_business_profile_add_session_expiry/up.sql new file mode 100644 index 00000000000..462dfd234ab --- /dev/null +++ b/migrations/2023-12-27-104559_business_profile_add_session_expiry/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS session_expiry BIGINT DEFAULT NULL; diff --git a/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql b/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql new file mode 100644 index 00000000000..c7c9cbeb401 --- /dev/null +++ b/migrations/2023-12-28-063619_add_enum_types_to_EventType/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql b/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql new file mode 100644 index 00000000000..74b87199c2f --- /dev/null +++ b/migrations/2023-12-28-063619_add_enum_types_to_EventType/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_authorized'; +ALTER TYPE "EventType" ADD VALUE IF NOT EXISTS 'payment_captured'; diff --git a/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/down.sql b/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/down.sql new file mode 100644 index 00000000000..c7c9cbeb401 --- /dev/null +++ b/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/up.sql b/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/up.sql new file mode 100644 index 00000000000..bb703fde397 --- /dev/null +++ b/migrations/2024-01-04-121733_add_dashboard_metadata_key_integration_completed/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TYPE "DashboardMetadata" ADD VALUE IF NOT EXISTS 'integration_completed'; \ No newline at end of file diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index f77638a43db..b2f5d3ea52c 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -382,6 +382,117 @@ ] } }, + "/blocklist": { + "get": { + "tags": [ + "Blocklist" + ], + "operationId": "List Blocked fingerprints of a particular kind", + "parameters": [ + { + "name": "data_kind", + "in": "query", + "description": "Kind of the fingerprint list requested", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlocklistDataKind" + } + } + ], + "responses": { + "200": { + "description": "Blocked Fingerprints", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "Blocklist" + ], + "operationId": "Block a Fingerprint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Fingerprint Blocked", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": [ + "Blocklist" + ], + "operationId": "Unblock a Fingerprint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Fingerprint Unblocked", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlocklistResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/customers": { "post": { "tags": [ @@ -473,15 +584,6 @@ "type": "string" } }, - { - "name": "customer_id", - "in": "path", - "description": "The unique identifier for the customer account", - "required": true, - "schema": { - "type": "string" - } - }, { "name": "accepted_country", "in": "query", @@ -711,15 +813,6 @@ "description": "List payment methods for a Customer\n\nTo filter and list the applicable payment methods for a particular Customer ID", "operationId": "List all Payment Methods for a Customer", "parameters": [ - { - "name": "customer_id", - "in": "path", - "description": "The unique identifier for the customer account", - "required": true, - "schema": { - "type": "string" - } - }, { "name": "accepted_country", "in": "query", @@ -2448,9 +2541,6 @@ "AchBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "bank_account_number", "bank_routing_number" ], @@ -2458,15 +2548,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "bank_account_number": { "type": "string", @@ -2900,9 +2997,6 @@ "BacsBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "bank_account_number", "bank_sort_code" ], @@ -2910,15 +3004,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "bank_account_number": { "type": "string", @@ -3413,13 +3514,17 @@ "eps": { "type": "object", "required": [ - "billing_details", "bank_name", "country" ], "properties": { "billing_details": { - "$ref": "#/components/schemas/BankRedirectBilling" + "allOf": [ + { + "$ref": "#/components/schemas/BankRedirectBilling" + } + ], + "nullable": true }, "bank_name": { "$ref": "#/components/schemas/BankNames" @@ -3473,13 +3578,17 @@ "ideal": { "type": "object", "required": [ - "billing_details", "bank_name", "country" ], "properties": { "billing_details": { - "$ref": "#/components/schemas/BankRedirectBilling" + "allOf": [ + { + "$ref": "#/components/schemas/BankRedirectBilling" + } + ], + "nullable": true }, "bank_name": { "$ref": "#/components/schemas/BankNames" @@ -4045,6 +4154,95 @@ } ] }, + "BlocklistDataKind": { + "type": "string", + "enum": [ + "payment_method", + "card_bin", + "extended_card_bin" + ] + }, + "BlocklistRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "card_bin" + ] + }, + "data": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fingerprint" + ] + }, + "data": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "extended_card_bin" + ] + }, + "data": { + "type": "string" + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "BlocklistResponse": { + "type": "object", + "required": [ + "fingerprint_id", + "data_kind", + "created_at" + ], + "properties": { + "fingerprint_id": { + "type": "string" + }, + "data_kind": { + "$ref": "#/components/schemas/BlocklistDataKind" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, "BoletoVoucherData": { "type": "object", "properties": { @@ -4055,6 +4253,22 @@ } } }, + "BusinessPaymentLinkConfig": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkConfigRequest" + }, + { + "type": "object", + "properties": { + "domain_name": { + "type": "string", + "nullable": true + } + } + } + ] + }, "CaptureMethod": { "type": "string", "enum": [ @@ -4380,6 +4594,7 @@ "payme", "paypal", "payu", + "placetopay", "powertranz", "prophetpay", "rapyd", @@ -4395,7 +4610,8 @@ "worldpay", "zen", "signifyd", - "plaid" + "plaid", + "riskified" ] }, "ConnectorMetadata": { @@ -5530,6 +5746,8 @@ "payment_failed", "payment_processing", "payment_cancelled", + "payment_authorized", + "payment_captured", "action_required", "refund_succeeded", "refund_failed", @@ -6566,6 +6784,27 @@ } } }, + "ListBlocklistQuery": { + "type": "object", + "required": [ + "data_kind" + ], + "properties": { + "data_kind": { + "$ref": "#/components/schemas/BlocklistDataKind" + }, + "limit": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "offset": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, "MandateAmountData": { "type": "object", "required": [ @@ -6724,6 +6963,18 @@ }, "status": { "$ref": "#/components/schemas/MandateStatus" + }, + "error_code": { + "type": "string", + "description": "If there was an error while calling the connectors the code is received here", + "example": "E0001", + "nullable": true + }, + "error_message": { + "type": "string", + "description": "If there was an error while calling the connector the error message is received here", + "example": "Failed while verifying the card", + "nullable": true } } }, @@ -6908,26 +7159,10 @@ "description": "The frm routing algorithm to be used for routing payments to desired FRM's", "nullable": true }, - "intent_fulfillment_time": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, "organization_id": { "type": "string", "description": "The id of the organization to which the merchant belongs to", "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentLinkConfig" - } - ], - "nullable": true } } }, @@ -7101,9 +7336,6 @@ }, "recon_status": { "$ref": "#/components/schemas/ReconStatus" - }, - "payment_link_config": { - "nullable": true } } }, @@ -7224,21 +7456,11 @@ "description": "The frm routing algorithm to be used for routing payments to desired FRM's", "nullable": true }, - "intent_fulfillment_time": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "nullable": true, - "minimum": 0 - }, "default_profile": { "type": "string", "description": "The default business profile that must be used for creating merchant accounts and payments\nTo unset this field, pass an empty string", "nullable": true, "maxLength": 64 - }, - "payment_link_config": { - "nullable": true } } }, @@ -8115,10 +8337,37 @@ "example": 1, "minimum": 0 }, + "requires_shipping": { + "type": "boolean", + "nullable": true + }, "product_img_link": { "type": "string", "description": "The image URL of the product", "nullable": true + }, + "product_id": { + "type": "string", + "description": "ID of the product that is being purchased", + "nullable": true + }, + "category": { + "type": "string", + "description": "Category of the product that is being purchased", + "nullable": true + }, + "brand": { + "type": "string", + "description": "Brand of the product that is being purchased", + "nullable": true + }, + "product_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ProductType" + } + ], + "nullable": true } } }, @@ -8148,10 +8397,37 @@ "format": "int64", "description": "the amount per quantity of product" }, + "requires_shipping": { + "type": "boolean", + "nullable": true + }, "product_img_link": { "type": "string", "description": "The image URL of the product", "nullable": true + }, + "product_id": { + "type": "string", + "description": "ID of the product that is being purchased", + "nullable": true + }, + "category": { + "type": "string", + "description": "Category of the product that is being purchased", + "nullable": true + }, + "brand": { + "type": "string", + "description": "Brand of the product that is being purchased", + "nullable": true + }, + "product_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ProductType" + } + ], + "nullable": true } } }, @@ -8537,6 +8813,21 @@ } } }, + "PaymentCreatePaymentLinkConfig": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkConfigRequest" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "PaymentExperience": { "type": "string", "enum": [ @@ -8601,35 +8892,63 @@ } ] }, - "PaymentLinkColorSchema": { + "PaymentLinkConfig": { "type": "object", + "required": [ + "theme", + "logo", + "seller_name", + "sdk_layout" + ], "properties": { - "background_primary_color": { + "theme": { "type": "string", - "nullable": true + "description": "custom theme for the payment link" }, - "sdk_theme": { + "logo": { "type": "string", - "nullable": true + "description": "merchant display logo" + }, + "seller_name": { + "type": "string", + "description": "Custom merchant name for payment link" + }, + "sdk_layout": { + "type": "string", + "description": "Custom layout for sdk" } } }, - "PaymentLinkConfig": { + "PaymentLinkConfigRequest": { "type": "object", "properties": { - "merchant_logo": { + "theme": { "type": "string", - "example": "https://i.imgur.com/RfxPFQo.png", + "description": "custom theme for the payment link", + "example": "#4E6ADD", "nullable": true, "maxLength": 255 }, - "color_scheme": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentLinkColorSchema" - } - ], - "nullable": true + "logo": { + "type": "string", + "description": "merchant display logo", + "example": "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg", + "nullable": true, + "maxLength": 255 + }, + "seller_name": { + "type": "string", + "description": "Custom merchant name for payment link", + "example": "hyperswitch", + "nullable": true, + "maxLength": 255 + }, + "sdk_layout": { + "type": "string", + "description": "Custom layout for sdk", + "example": "accordion", + "nullable": true, + "maxLength": 255 } } }, @@ -8648,31 +8967,6 @@ } } }, - "PaymentLinkObject": { - "type": "object", - "required": [ - "payment_link_config" - ], - "properties": { - "link_expiry": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "merchant_custom_domain_name": { - "type": "string", - "nullable": true - }, - "payment_link_config": { - "$ref": "#/components/schemas/PaymentLinkConfig" - }, - "custom_merchant_name": { - "type": "string", - "description": "Custom merchant name for payment link", - "nullable": true - } - } - }, "PaymentLinkResponse": { "type": "object", "required": [ @@ -8688,6 +8982,13 @@ } } }, + "PaymentLinkStatus": { + "type": "string", + "enum": [ + "active", + "expired" + ] + }, "PaymentListConstraints": { "type": "object", "properties": { @@ -8846,6 +9147,14 @@ "description": "The card network", "example": "Visa", "nullable": true + }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true } } }, @@ -9182,6 +9491,14 @@ "description": "A timestamp (ISO 8601 code) that determines when the customer was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true + }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true } } }, @@ -9292,6 +9609,14 @@ ], "nullable": true }, + "bank_transfer": { + "allOf": [ + { + "$ref": "#/components/schemas/Bank" + } + ], + "nullable": true + }, "metadata": { "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", @@ -9754,10 +10079,17 @@ ], "nullable": true }, - "payment_link_object": { + "payment_link": { + "type": "boolean", + "description": "Whether to get the payment link (if applicable)", + "default": false, + "example": true, + "nullable": true + }, + "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentLinkObject" + "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" } ], "nullable": true @@ -9787,6 +10119,18 @@ "type": "boolean", "description": "Request for an incremental authorization", "nullable": true + }, + "session_expiry": { + "type": "integer", + "format": "int32", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", + "example": 900, + "nullable": true, + "minimum": 0 + }, + "frm_metadata": { + "description": "additional data related to some frm connectors", + "nullable": true } } }, @@ -10124,10 +10468,17 @@ ], "nullable": true }, - "payment_link_object": { + "payment_link": { + "type": "boolean", + "description": "Whether to get the payment link (if applicable)", + "default": false, + "example": true, + "nullable": true + }, + "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentLinkObject" + "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" } ], "nullable": true @@ -10157,6 +10508,18 @@ "type": "boolean", "description": "Request for an incremental authorization", "nullable": true + }, + "session_expiry": { + "type": "integer", + "format": "int32", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", + "example": 900, + "nullable": true, + "minimum": 0 + }, + "frm_metadata": { + "description": "additional data related to some frm connectors", + "nullable": true } } }, @@ -10165,6 +10528,7 @@ "required": [ "status", "amount", + "net_amount", "currency", "payment_method", "attempt_count" @@ -10199,6 +10563,12 @@ "description": "The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", "example": 100 }, + "net_amount": { + "type": "integer", + "format": "int64", + "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount", + "example": 110 + }, "amount_capturable": { "type": "integer", "format": "int64", @@ -10609,6 +10979,18 @@ }, "description": "List of incremental authorizations happened to the payment", "nullable": true + }, + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Date Time expiry of the payment", + "example": "2022-09-10T10:11:12Z", + "nullable": true + }, + "fingerprint": { + "type": "string", + "description": "Payment Fingerprint", + "nullable": true } } }, @@ -11267,6 +11649,17 @@ } } }, + "ProductType": { + "type": "string", + "enum": [ + "physical", + "digital", + "travel", + "ride", + "event", + "accommodation" + ] + }, "ReceiverDetails": { "type": "object", "required": [ @@ -11546,6 +11939,12 @@ }, "profile_id": { "type": "string", + "description": "The id of business profile for this refund", + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "The merchant_connector_id of the processor through which this payment went through", "nullable": true } } @@ -11787,7 +12186,7 @@ "type": "string", "format": "date-time" }, - "link_expiry": { + "expiry": { "type": "string", "format": "date-time", "nullable": true @@ -11797,7 +12196,7 @@ "nullable": true }, "status": { - "type": "string" + "$ref": "#/components/schemas/PaymentLinkStatus" }, "currency": { "allOf": [ @@ -11927,9 +12326,6 @@ "SepaBankTransfer": { "type": "object", "required": [ - "bank_name", - "bank_country_code", - "bank_city", "iban", "bic" ], @@ -11937,15 +12333,22 @@ "bank_name": { "type": "string", "description": "Bank name", - "example": "Deutsche Bank" + "example": "Deutsche Bank", + "nullable": true }, "bank_country_code": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "bank_city": { "type": "string", "description": "Bank city", - "example": "California" + "example": "California", + "nullable": true }, "iban": { "type": "string", diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json index fe25f6f5e68..375c1f0df52 100644 --- a/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/adyen_uk/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -56,7 +56,12 @@ "payment_method_types": [ { "payment_method_type": "credit", - "card_networks": ["Visa", "Mastercard"], + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, @@ -64,7 +69,12 @@ }, { "payment_method_type": "debit", - "card_networks": ["Visa", "Mastercard"], + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/event.test.js index 93bd98b4ccb..1e8dabda827 100644 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/event.test.js +++ b/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/event.test.js @@ -75,12 +75,12 @@ if (jsonData?.error?.type) { ); } -// Response body should have value "invalid_request" for "error type" -if (jsonData?.error?.message) { +// Response body should have value "connector error" for "error type" +if (jsonData?.error?.type) { pm.test( - "[POST]::/payments - Content check if value for 'error.message' matches 'Card Expired'", + "[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'", function () { - pm.expect(jsonData.error.message).to.eql("Card Expired"); + pm.expect(jsonData.error.type).to.eql("invalid_request"); }, ); } diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Refunds - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Refunds - Create/event.test.js index a195cd43879..24327b0aad0 100644 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Refunds - Create/event.test.js +++ b/postman/collection-dir/adyen_uk/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Refunds - Create/event.test.js @@ -50,10 +50,10 @@ if (jsonData?.error?.type) { // Response body should have value "invalid_request" for "error type" if (jsonData?.error?.message) { pm.test( - "[POST]::/payments - Content check if value for 'error.message' matches 'Refund amount exceeds the payment amount'", + "[POST]::/payments - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'", function () { pm.expect(jsonData.error.message).to.eql( - "Refund amount exceeds the payment amount", + "The refund amount exceeds the amount captured", ); }, ); diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js index ac3f862e43f..e7f91410f31 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" because payment gets succeeded after one day. if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'processing'", + "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json index 21f05484389..e37391b78b5 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js index a6976d95f69..81ce7784e83 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js index b160ad9dc04..9b9d104698f 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -86,9 +86,9 @@ if (jsonData?.amount) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'", + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json index b1d5ad5ebbf..c2a8a615d1c 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js index f87069589f0..80343f371a6 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js index 255743af78c..91c871e2160 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js @@ -65,9 +65,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js index 4fbefdb8494..ef09f8071d6 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js index fa6deebe16a..ea70c2aace0 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js @@ -63,12 +63,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "processing" for "status" +// Response body should have value "partially_captured" for "status" if (jsonData?.status) { pm.test( - "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'processing'", + "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("partially_captured"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json index 5e3ff0e70ad..878ea581a93 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "manual", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js index b1b53a360e3..cc4a60507a8 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "processing" for "status" +// Response body should have value "partially_captured" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'processing'", + "[POST]::/payments - Content check if value for 'status' matches 'partially_captured'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("partially_captured"); }, ); } diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/.meta.json new file mode 100644 index 00000000000..6626732a3ca --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/.meta.json @@ -0,0 +1,12 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Capture", + "Payments - Retrieve", + "Refunds - Create", + "Refunds - Create-copy", + "Refunds - Retrieve Copy", + "Refunds - Validation should throw", + "Payments - Retrieve Copy" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/event.test.js new file mode 100644 index 00000000000..96d98780785 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/event.test.js @@ -0,0 +1,105 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/capture - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/capture - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/capture - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "partially_captured" for "status" +if (jsonData?.status) { + pm.test( + "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'", + function () { + pm.expect(jsonData.status).to.eql("partially_captured"); + }, + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", + function () { + pm.expect(jsonData.amount).to.eql(6540); + }, + ); +} + +// Response body should have value "6000" for "amount_received" +if (jsonData?.amount_received) { + pm.test( + "[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'", + function () { + pm.expect(jsonData.amount_received).to.eql(6000); + }, + ); +} + +// Response body should have value "0" for "amount_received" +if (jsonData?.amount_capturable) { + pm.test( + "[POST]::/payments:id/capture - Content check if value for 'amount_capturable' matches '0'", + function () { + pm.expect(jsonData.amount_capturable).to.eql(0); + }, + ); +} + diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/request.json new file mode 100644 index 00000000000..8975575ca40 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/request.json @@ -0,0 +1,45 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount_to_capture": 6000, + "statement_descriptor_name": "Joseph", + "statement_descriptor_suffix": "JS" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/capture", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To capture the funds for an uncaptured payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Capture/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/event.test.js rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/event.test.js diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/request.json new file mode 100644 index 00000000000..4cbb79aa56a --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/request.json @@ -0,0 +1,94 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "capture_method": "manual", + "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", + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "sundari" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari", + "last_name": "sundari" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "stripe" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Create/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/event.test.js new file mode 100644 index 00000000000..3342e5b2530 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/event.test.js @@ -0,0 +1,85 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "partially_captured" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'partially_captured'", + function () { + pm.expect(jsonData.status).to.eql("partially_captured"); + }, + ); +} + +// Check if the "refunds" array exists +pm.test("Check if 'refunds' array exists", function() { + pm.expect(jsonData.refunds).to.be.an("array"); +}); + +// Check if there are exactly 2 items in the "refunds" array +pm.test("Check if there are 2 refunds", function() { + pm.expect(jsonData.refunds.length).to.equal(2); +}); + + + + diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/request.json similarity index 86% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/request.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve Copy/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..89b1355575a --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "partially_captured" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'partially_captured'", + function () { + pm.expect(jsonData.status).to.eql("partially_captured"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/request.json similarity index 86% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/request.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/request.json +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Payments - Retrieve/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/event.test.js new file mode 100644 index 00000000000..b0a888ae70d --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/event.test.js @@ -0,0 +1,50 @@ +// Validate status 2xx +pm.test("[POST]::/refunds - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/refunds - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id +if (jsonData?.refund_id) { + pm.collectionVariables.set("refund_id", jsonData.refund_id); + console.log( + "- use {{refund_id}} as collection variable for value", + jsonData.refund_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'amount' matches '6540'", + function () { + pm.expect(jsonData.amount).to.eql(2000); + }, + ); +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/request.json new file mode 100644 index 00000000000..ff371b247db --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/request.json @@ -0,0 +1,42 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_id": "{{payment_id}}", + "amount": 2000, + "reason": "Customer returned product", + "refund_type": "instant", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create-copy/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/event.test.js new file mode 100644 index 00000000000..ccc9bf47022 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/event.test.js @@ -0,0 +1,50 @@ +// Validate status 2xx +pm.test("[POST]::/refunds - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/refunds - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id +if (jsonData?.refund_id) { + pm.collectionVariables.set("refund_id", jsonData.refund_id); + console.log( + "- use {{refund_id}} as collection variable for value", + jsonData.refund_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'amount' matches '4000'", + function () { + pm.expect(jsonData.amount).to.eql(4000); + }, + ); +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/request.json new file mode 100644 index 00000000000..933f1a66eda --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/request.json @@ -0,0 +1,42 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_id": "{{payment_id}}", + "amount": 4000, + "reason": "Customer returned product", + "refund_type": "instant", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Create/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/event.test.js new file mode 100644 index 00000000000..072e259d834 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/event.test.js @@ -0,0 +1,50 @@ +// Validate status 2xx +pm.test("[GET]::/refunds/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/refunds/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id +if (jsonData?.refund_id) { + pm.collectionVariables.set("refund_id", jsonData.refund_id); + console.log( + "- use {{refund_id}} as collection variable for value", + jsonData.refund_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} + +// Response body should have value "2000" for "amount" +if (jsonData?.status) { + pm.test( + "[POST]::/refunds - Content check if value for 'amount' matches '2000'", + function () { + pm.expect(jsonData.amount).to.eql(2000); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/request.json similarity index 84% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/request.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/request.json +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Retrieve Copy/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/.event.meta.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/event.test.js b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/event.test.js new file mode 100644 index 00000000000..2f5de724061 --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/event.test.js @@ -0,0 +1,41 @@ +// Validate status 2xx +pm.test("[POST]::/refunds - Status code is 4xx", function () { + pm.response.to.be.error; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/refunds - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id +if (jsonData?.refund_id) { + pm.collectionVariables.set("refund_id", jsonData.refund_id); + console.log( + "- use {{refund_id}} as collection variable for value", + jsonData.refund_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.error.message) { + pm.test( + "[POST]::/refunds - Content check if value for 'message' matches 'The refund amount exceeds the amount captured'", + function () { + pm.expect(jsonData.error.message).to.eql("The refund amount exceeds the amount captured"); + }, + ); +} + diff --git a/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/request.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/request.json new file mode 100644 index 00000000000..ff371b247db --- /dev/null +++ b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/request.json @@ -0,0 +1,42 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_id": "{{payment_id}}", + "amount": 2000, + "reason": "Customer returned product", + "refund_type": "instant", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/response.json b/postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/response.json rename to postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario28-Create partially captured payment with refund/Refunds - Validation should throw/response.json diff --git a/postman/collection-dir/bluesnap/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/bluesnap/Flow Testcases/QuickStart/Payment Connector - Create/request.json index ef3bdcd3aa6..4609a010054 100644 --- a/postman/collection-dir/bluesnap/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/bluesnap/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -53,7 +53,25 @@ "payment_method_types": [ { "payment_method_type": "credit", - "card_networks": ["Visa", "Mastercard"], + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "debit", + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, diff --git a/postman/collection-dir/checkout/.event.meta.json b/postman/collection-dir/checkout/.event.meta.json index eb871bbcb9b..2df9d47d936 100644 --- a/postman/collection-dir/checkout/.event.meta.json +++ b/postman/collection-dir/checkout/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.prerequest.js", "event.test.js"] + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/.info.json b/postman/collection-dir/checkout/.info.json index d061408c8ab..4c3a1efad42 100644 --- a/postman/collection-dir/checkout/.info.json +++ b/postman/collection-dir/checkout/.info.json @@ -1,9 +1,10 @@ { "info": { - "_postman_id": "0da8c3be-4466-413e-9e72-81b21533423e", + "_postman_id": "9ab8f157-6b4b-430a-9ca8-34931682f988", "name": "Checkout Collection", "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "25737662" + "_exporter_id": "25180378", + "_collection_link": "https://galactic-desert-365744.postman.co/workspace/postman-tests-for-all-connector~f274d40a-132c-47e3-8240-6793392ee4d1/collection/25180378-9ab8f157-6b4b-430a-9ca8-34931682f988?action=share&creator=25180378&source=collection_link" } } diff --git a/postman/collection-dir/checkout/.meta.json b/postman/collection-dir/checkout/.meta.json index d513035ce2d..91b6a65c5bc 100644 --- a/postman/collection-dir/checkout/.meta.json +++ b/postman/collection-dir/checkout/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Health check", "Flow Testcases"] + "childrenOrder": [ + "Health check", + "Flow Testcases" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/.meta.json b/postman/collection-dir/checkout/Flow Testcases/.meta.json index 023989e1e49..1bbce843680 100644 --- a/postman/collection-dir/checkout/Flow Testcases/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/.meta.json @@ -1,3 +1,7 @@ { - "childrenOrder": ["QuickStart", "Happy Cases", "Variation Cases"] + "childrenOrder": [ + "QuickStart", + "Happy Cases", + "Variation Cases" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/.meta.json index b2d6cbdc549..bf036d3b736 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/.meta.json @@ -1,5 +1,9 @@ { "childrenOrder": [ + "Scenario11-Save card flow", + "Scenario12-Don't Pass CVV for save card flow and verify success payment", + "Scenario13-Pass Invalid CVV for save card flow and verify failed payment", + "Scenario14-Save card payment with manual capture", "Scenario1-Create payment with confirm true", "Scenario2-Create payment with confirm false", "Scenario3-Create payment without PMD", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json index 69b505c6d86..60051ecca22 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Retrieve"] + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js index cb7a76331cd..30eaca5c0ca 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json index 144a35f773a..4e96529fe9a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -30,11 +30,11 @@ "phone_country_code": "+1", "description": "Its my first payment request", "authentication_type": "no_three_ds", - "return_url": "https://duck.com", + "return_url": "https://google.com", "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4005519200000004", + "card_number": "4485040371536584", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/.meta.json index 69b505c6d86..60051ecca22 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Retrieve"] + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/request.json index f5f3112ab99..197580ddffa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Create/request.json @@ -80,8 +80,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js index e69de29bb2d..97b68c987bd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/request.json index 3a1d8aa178f..f64e37a125a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Cancel/request.json @@ -23,8 +23,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/cancel", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "cancel"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "cancel" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/request.json index 2c085d1319b..b28dec99902 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Create/request.json @@ -80,8 +80,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js index e69de29bb2d..97b68c987bd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/request.json index 2c085d1319b..b28dec99902 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Payments - Create/request.json @@ -80,8 +80,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/request.json index 491b7d68feb..ff371b247db 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Refund After Partial Capture/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/request.json index 2c085d1319b..b28dec99902 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Create/request.json @@ -80,8 +80,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js index e69de29bb2d..97b68c987bd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/request.json index 38ea8b2b84e..d10988d49b1 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true&expand_captures=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js index e69de29bb2d..97b68c987bd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 1/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 2/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/request.json index e67be321142..127549802b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Capture - 3/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/request.json index 2c085d1319b..b28dec99902 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Create/request.json @@ -80,8 +80,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js index e69de29bb2d..97b68c987bd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/request.json index 38ea8b2b84e..d10988d49b1 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true&expand_captures=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/request.json index 96cd9f45b0f..9e0bc932afb 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Refunds - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Create/event.test.js index b7c7c3384d9..74bc3f6ab78 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Create/event.test.js @@ -49,12 +49,12 @@ if (jsonData?.customer_id) { console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.'); }; -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Refunds - Create Copy/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Refunds - Create Copy/.event.meta.json index 688c85746ef..2df9d47d936 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Refunds - Create Copy/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Refunds - Create Copy/.event.meta.json @@ -1,5 +1,6 @@ { "eventOrder": [ + "event.prerequest.js", "event.test.js" ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Save card payments - Confirm/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Save card payments - Confirm/event.test.js index 22d88c07e50..4f38b75d82f 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Save card payments - Confirm/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Save card payments - Confirm/event.test.js @@ -63,12 +63,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Save card payments - Confirm/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Save card payments - Confirm/event.test.js index c5b17c6e79a..82a20684b26 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Save card payments - Confirm/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Save card payments - Confirm/event.test.js @@ -63,16 +63,16 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } -// Response body should have value "adyen" for "connector" +// Response body should have value "checkout" for "connector" if (jsonData?.connector) { pm.test( "[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'checkout'", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/.meta.json index 4bacbe0f555..fc583d47a25 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/.meta.json @@ -6,8 +6,6 @@ "Save card payments - Create", "Save card payments - Confirm", "Payments - Capture", - "Payments - Retrieve-copy", - "Refunds - Create Copy", - "Refunds - Retrieve Copy" + "Payments - Retrieve-copy" ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Create/event.test.js index b7c7c3384d9..74bc3f6ab78 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Create/event.test.js @@ -49,12 +49,12 @@ if (jsonData?.customer_id) { console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.'); }; -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js index 8a41b0ef7e5..bc42544064b 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -63,12 +63,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json index 98eb4829e4b..cffc5ba2ed3 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json @@ -43,8 +43,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json index a6a8150c240..762826dac09 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -30,7 +30,7 @@ "phone_country_code": "+1", "description": "Its my first payment request", "authentication_type": "no_three_ds", - "return_url": "https://duck.com", + "return_url": "https://google.com", "payment_method": "card", "payment_method_data": { "card": { @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js index 2fd89553c9c..71acfe97494 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js @@ -63,12 +63,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json index dd704a5de76..cc9d7304aa3 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json @@ -53,8 +53,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json index e3b1e235042..b28abd0c309 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json @@ -67,8 +67,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/request.json index 9fe257ed85e..8975575ca40 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/request.json index 1c3a9e08ac3..3895e38ff26 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Confirm/request.json @@ -43,8 +43,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/request.json index 50cb0663b40..de5aa29f7b0 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/request.json index cceb2b55f0a..8efb99d3c90 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/request.json index 5b606850fd2..c4f2248a4f4 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/request.json index 9fe257ed85e..8975575ca40 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/request.json index 5b606850fd2..c4f2248a4f4 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/request.json index 3a1d8aa178f..f64e37a125a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Cancel/request.json @@ -23,8 +23,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/cancel", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "cancel"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "cancel" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/request.json index 5b606850fd2..c4f2248a4f4 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/event.test.js index 2f020d7ff20..4b3558a20cc 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/request.json index 144a35f773a..47f08f9f372 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/request.json index 5f4c58816d5..5e306df7a55 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Refunds - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/event.test.js index 2f020d7ff20..4b3558a20cc 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/request.json index 144a35f773a..47f08f9f372 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/.event.meta.json index 0731450e6b2..2df9d47d936 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/request.json index caed7818578..b56057fad5d 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create-copy/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/.event.meta.json index 0731450e6b2..2df9d47d936 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/request.json index 9fe125ce8ea..d18aaf8befd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Refunds - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/request.json index 6ceefe5d24c..4e4c6628497 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/API Key - Create/request.json @@ -35,8 +35,13 @@ }, "url": { "raw": "{{baseUrl}}/api_keys/:merchant_id", - "host": ["{{baseUrl}}"], - "path": ["api_keys", ":merchant_id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api_keys", + ":merchant_id" + ], "variable": [ { "key": "merchant_id", diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/request.json index dcbf46ee538..ffeea3410a4 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Merchant Account - Create/request.json @@ -84,8 +84,12 @@ }, "url": { "raw": "{{baseUrl}}/accounts", - "host": ["{{baseUrl}}"], - "path": ["accounts"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "accounts" + ] }, "description": "Create a new account for a merchant. The merchant could be a seller or retailer or client who likes to receive and send payments." } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/request.json index 0f14659a5e8..7ca3d82e793 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -56,7 +56,12 @@ "payment_method_types": [ { "payment_method_type": "credit", - "card_networks": ["Visa", "Mastercard"], + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, @@ -64,7 +69,12 @@ }, { "payment_method_type": "debit", - "card_networks": ["Visa", "Mastercard"], + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"], "minimum_amount": 1, "maximum_amount": 68607706, "recurring_enabled": true, @@ -94,8 +104,14 @@ }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", - "host": ["{{baseUrl}}"], - "path": ["account", ":account_id", "connectors"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors" + ], "variable": [ { "key": "account_id", diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/request.json index 07ffc4eedef..872fac0738c 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Create/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/request.json index ef0b213739d..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/request.json @@ -7,9 +7,20 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/request.json index 5f4c58816d5..5e306df7a55 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Refunds - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json index 9cbdbc8e22f..ad281c58256 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json index 6e9db26a339..03e71d6c7ea 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/request.json index 418f77270d9..2d0e17e60e8 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid card number)/request.json @@ -75,8 +75,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/request.json index 0b35b7a4e92..bd5526d6f86 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(invalid CVV)/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/.meta.json index f2bee31ef66..90b19864ee1 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Confirm"] + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/request.json index 98eb4829e4b..cffc5ba2ed3 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/request.json @@ -43,8 +43,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/request.json index e3b1e235042..b28abd0c309 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Create/request.json @@ -67,8 +67,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/request.json index 6c934a9132c..766a8330d6e 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json index 5b606850fd2..c4f2248a4f4 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/.meta.json index 6df7c0a1943..cafd3a6808e 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Capture"] + "childrenOrder": [ + "Payments - Create", + "Payments - Capture" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/request.json index 6c934a9132c..766a8330d6e 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Capture/request.json @@ -25,8 +25,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/capture", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "capture"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/event.test.js index c48d8e2d054..16c37817f0b 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json index 144a35f773a..47f08f9f372 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/.meta.json index 5ff1a7cb4e8..afba25ab6d6 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Cancel"] + "childrenOrder": [ + "Payments - Create", + "Payments - Cancel" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/request.json index 3a1d8aa178f..f64e37a125a 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Cancel/request.json @@ -23,8 +23,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/cancel", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "cancel"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "cancel" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/event.test.js index c48d8e2d054..16c37817f0b 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json index 09987daa71e..06284e30aed 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/event.test.js index 2f020d7ff20..4b3558a20cc 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "succeeded" for "status" +// Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + "[POST]::/payments - Content check if value for 'status' matches 'processing'", function () { - pm.expect(jsonData.status).to.eql("succeeded"); + pm.expect(jsonData.status).to.eql("processing"); }, ); } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/request.json index 144a35f773a..47f08f9f372 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Create/request.json @@ -76,8 +76,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/.event.meta.json index 0731450e6b2..2df9d47d936 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/event.test.js index 00f93b844c5..d5f63c170ff 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/event.test.js @@ -47,13 +47,13 @@ if (jsonData?.error?.type) { ); } -// Response body should have value "Refund amount exceeds the payment amount" for "message" +// Response body should have value "The refund amount exceeds the amount captured" for "message" if (jsonData?.error?.message) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'Refund amount exceeds the payment amount'", + "[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'", function () { pm.expect(jsonData.error.message).to.eql( - "Refund amount exceeds the payment amount", + "The refund amount exceeds the amount captured", ); }, ); diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/request.json index 10de81bed08..326319e8fdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/request.json index 45feadeb3c3..758498447dd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Create/request.json @@ -77,8 +77,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 00000000000..97b68c987bd --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/.event.meta.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/.event.meta.json index 0731450e6b2..2df9d47d936 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/request.json b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/request.json index 9fe125ce8ea..d18aaf8befd 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/request.json +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/checkout/Health check/.meta.json b/postman/collection-dir/checkout/Health check/.meta.json index 579f9fd541c..66ee7e50cab 100644 --- a/postman/collection-dir/checkout/Health check/.meta.json +++ b/postman/collection-dir/checkout/Health check/.meta.json @@ -1,3 +1,5 @@ { - "childrenOrder": ["New Request"] + "childrenOrder": [ + "New Request" + ] } diff --git a/postman/collection-dir/checkout/Health check/New Request/.event.meta.json b/postman/collection-dir/checkout/Health check/New Request/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/checkout/Health check/New Request/.event.meta.json +++ b/postman/collection-dir/checkout/Health check/New Request/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/checkout/Health check/New Request/request.json b/postman/collection-dir/checkout/Health check/New Request/request.json index e40e9396178..4cc8d4b1a96 100644 --- a/postman/collection-dir/checkout/Health check/New Request/request.json +++ b/postman/collection-dir/checkout/Health check/New Request/request.json @@ -10,7 +10,11 @@ ], "url": { "raw": "{{baseUrl}}/health", - "host": ["{{baseUrl}}"], - "path": ["health"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] } } diff --git a/postman/collection-dir/prophetpay/.auth.json b/postman/collection-dir/prophetpay/.auth.json new file mode 100644 index 00000000000..915a2835790 --- /dev/null +++ b/postman/collection-dir/prophetpay/.auth.json @@ -0,0 +1,22 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + } +} diff --git a/postman/collection-dir/prophetpay/.event.meta.json b/postman/collection-dir/prophetpay/.event.meta.json new file mode 100644 index 00000000000..eb871bbcb9b --- /dev/null +++ b/postman/collection-dir/prophetpay/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.prerequest.js", "event.test.js"] +} diff --git a/postman/collection-dir/prophetpay/.info.json b/postman/collection-dir/prophetpay/.info.json new file mode 100644 index 00000000000..5c4524a2797 --- /dev/null +++ b/postman/collection-dir/prophetpay/.info.json @@ -0,0 +1,9 @@ +{ + "info": { + "_postman_id": "a553df38-fa33-4522-b029-1cd32821730e", + "name": "Prophetpay Postman Collection", + "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "24206034" + } +} diff --git a/postman/collection-dir/prophetpay/.meta.json b/postman/collection-dir/prophetpay/.meta.json new file mode 100644 index 00000000000..91b6a65c5bc --- /dev/null +++ b/postman/collection-dir/prophetpay/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Health check", + "Flow Testcases" + ] +} diff --git a/postman/collection-dir/prophetpay/.variable.json b/postman/collection-dir/prophetpay/.variable.json new file mode 100644 index 00000000000..af2658909e7 --- /dev/null +++ b/postman/collection-dir/prophetpay/.variable.json @@ -0,0 +1,96 @@ +{ + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + }, + { + "key": "admin_api_key", + "value": "", + "type": "string" + }, + { + "key": "api_key", + "value": "", + "type": "string" + }, + { + "key": "merchant_id", + "value": "" + }, + { + "key": "payment_id", + "value": "" + }, + { + "key": "customer_id", + "value": "" + }, + { + "key": "mandate_id", + "value": "" + }, + { + "key": "payment_method_id", + "value": "" + }, + { + "key": "refund_id", + "value": "" + }, + { + "key": "merchant_connector_id", + "value": "" + }, + { + "key": "client_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_api_key", + "value": "", + "type": "string" + }, + { + "key": "connector_api_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_key1", + "value": "", + "type": "string" + }, + { + "key": "publishable_key", + "value": "", + "type": "string" + }, + { + "key": "api_key_id", + "value": "", + "type": "string" + }, + { + "key": "payment_token", + "value": "" + }, + { + "key": "gateway_merchant_id", + "value": "", + "type": "string" + }, + { + "key": "certificate", + "value": "", + "type": "string" + }, + { + "key": "certificate_keys", + "value": "", + "type": "string" + } + ] +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/.meta.json new file mode 100644 index 00000000000..5e5d7fb5881 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/.meta.json @@ -0,0 +1,3 @@ +{ + "childrenOrder": ["QuickStart", "Happy Cases"] +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/.meta.json new file mode 100644 index 00000000000..1563aa6b43a --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Scenario1-Create payment with confirm true", + "Scenario2-Create payment with confirm false" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js new file mode 100644 index 00000000000..8d85991a308 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json new file mode 100644 index 00000000000..2c8566c2428 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -0,0 +1,47 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 8000, + "currency": "USD", + "confirm": true, + "amount_to_capture": 8000, + "business_country": "US", + "customer_id": "not_a_rick_roll", + "return_url": "https://www.google.com", + "payment_method": "card_redirect", + "payment_method_type": "card_redirect", + "payment_method_data": { + "card_redirect": { + "card_redirect": {} + } + }, + "routing": { + "type": "single", + "data": "prophetpay" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/response.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..02ab01502c1 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/request.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/request.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/request.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/response.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/event.prerequest.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/event.prerequest.js rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js new file mode 100644 index 00000000000..6c20960e9dc --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -0,0 +1,74 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json new file mode 100644 index 00000000000..2370350edca --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json @@ -0,0 +1,70 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "card_redirect", + "payment_method_type": "card_redirect", + "payment_method_data": { + "card_redirect": { + "card_redirect": {} + } + }, + "client_secret": "{{client_secret}}" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/response.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/response.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/event.prerequest.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/event.prerequest.js rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/event.test.js rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json new file mode 100644 index 00000000000..851a18115f9 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -0,0 +1,40 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 8000, + "currency": "USD", + "confirm": false, + "amount_to_capture": 8000, + "business_country": "US", + "customer_id": "not_a_rick_roll", + "return_url": "https://www.google.com", + "routing": { + "type": "single", + "data": "prophetpay" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/response.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/response.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/event.test.js rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/request.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json similarity index 86% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/request.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/request.json +++ b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/response.json b/postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/response.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/.meta.json new file mode 100644 index 00000000000..e3596ba357b --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/.meta.json @@ -0,0 +1,9 @@ +{ + "childrenOrder": [ + "Merchant Account - Create", + "API Key - Create", + "Payment Connector - Create", + "Payments - Create", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/event.test.js new file mode 100644 index 00000000000..4e27c5a5025 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/event.test.js @@ -0,0 +1,46 @@ +// Validate status 2xx +pm.test("[POST]::/api_keys/:merchant_id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/api_keys/:merchant_id - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id +if (jsonData?.key_id) { + pm.collectionVariables.set("api_key_id", jsonData.key_id); + console.log( + "- use {{api_key_id}} as collection variable for value", + jsonData.key_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.", + ); +} + +// pm.collectionVariables - Set api_key as variable for jsonData.api_key +if (jsonData?.api_key) { + pm.collectionVariables.set("api_key", jsonData.api_key); + console.log( + "- use {{api_key}} as collection variable for value", + jsonData.api_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.", + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/request.json new file mode 100644 index 00000000000..6ceefe5d24c --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/request.json @@ -0,0 +1,47 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw_json_formatted": { + "name": "API Key 1", + "description": null, + "expiration": "2069-09-23T01:02:03.000Z" + } + }, + "url": { + "raw": "{{baseUrl}}/api_keys/:merchant_id", + "host": ["{{baseUrl}}"], + "path": ["api_keys", ":merchant_id"], + "variable": [ + { + "key": "merchant_id", + "value": "{{merchant_id}}" + } + ] + } +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/response.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/API Key - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js new file mode 100644 index 00000000000..7de0d5beb31 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/event.test.js @@ -0,0 +1,56 @@ +// Validate status 2xx +pm.test("[POST]::/accounts - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/accounts - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_id as variable for jsonData.merchant_id +if (jsonData?.merchant_id) { + pm.collectionVariables.set("merchant_id", jsonData.merchant_id); + console.log( + "- use {{merchant_id}} as collection variable for value", + jsonData.merchant_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_id}}, as jsonData.merchant_id is undefined.", + ); +} + +// pm.collectionVariables - Set api_key as variable for jsonData.api_key +if (jsonData?.api_key) { + pm.collectionVariables.set("api_key", jsonData.api_key); + console.log( + "- use {{api_key}} as collection variable for value", + jsonData.api_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.", + ); +} + +// pm.collectionVariables - Set publishable_key as variable for jsonData.publishable_key +if (jsonData?.publishable_key) { + pm.collectionVariables.set("publishable_key", jsonData.publishable_key); + console.log( + "- use {{publishable_key}} as collection variable for value", + jsonData.publishable_key, + ); +} else { + console.log( + "INFO - Unable to assign variable {{publishable_key}}, as jsonData.publishable_key is undefined.", + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/request.json new file mode 100644 index 00000000000..3d331205b0a --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/request.json @@ -0,0 +1,91 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "merchant_id": "postman_merchant_GHAction_{{$guid}}", + "locker_id": "m0010", + "merchant_name": "NewAge Retailer", + "merchant_details": { + "primary_contact_person": "John Test", + "primary_email": "JohnTest@test.com", + "primary_phone": "sunt laborum", + "secondary_contact_person": "John Test2", + "secondary_email": "JohnTest2@test.com", + "secondary_phone": "cillum do dolor id", + "website": "www.example.com", + "about_business": "Online Retail with a wide selection of organic products for North America", + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US" + } + }, + "return_url": "https://duck.com", + "webhook_details": { + "webhook_version": "1.0.1", + "webhook_username": "ekart_retail", + "webhook_password": "password_ekart@123", + "payment_created_enabled": true, + "payment_succeeded_enabled": true, + "payment_failed_enabled": true + }, + "sub_merchants_enabled": false, + "metadata": { + "city": "NY", + "unit": "245" + }, + "primary_business_details": [ + { + "country": "US", + "business": "default" + } + ] + } + }, + "url": { + "raw": "{{baseUrl}}/accounts", + "host": ["{{baseUrl}}"], + "path": ["accounts"] + }, + "description": "Create a new account for a merchant. The merchant could be a seller or retailer or client who likes to receive and send payments." +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/response.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Merchant Account - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js new file mode 100644 index 00000000000..88e92d8d84a --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/event.test.js @@ -0,0 +1,39 @@ +// Validate status 2xx +pm.test( + "[POST]::/account/:account_id/connectors - Status code is 2xx", + function () { + pm.response.to.be.success; + }, +); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/account/:account_id/connectors - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id +if (jsonData?.merchant_connector_id) { + pm.collectionVariables.set( + "merchant_connector_id", + jsonData.merchant_connector_id, + ); + console.log( + "- use {{merchant_connector_id}} as collection variable for value", + jsonData.merchant_connector_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.", + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/request.json new file mode 100644 index 00000000000..09a2a5866ad --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -0,0 +1,90 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "connector_type": "fiz_operations", + "connector_name": "prophetpay", + "connector_account_details": { + "auth_type": "SignatureKey", + "api_key": "{{connector_api_key}}", + "api_secret": "{{connector_api_secret}}", + "key1": "{{connector_key1}}" + }, + "business_country": "US", + "business_label": "default", + "test_mode": false, + "disabled": false, + "payment_methods_enabled": [ + { + "payment_method": "card_redirect", + "payment_method_types": [ + { + "payment_method_type": "card_redirect", + "payment_experience": "redirect_to_url", + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + } + ], + "metadata": { + "city": "NY", + "unit": "245" + } + } + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors", + "host": ["{{baseUrl}}"], + "path": ["account", ":account_id", "connectors"], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}", + "description": "(Required) The unique identifier for the merchant account" + } + ] + }, + "description": "Create a new Payment Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialised services like Fraud / Accounting etc." +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/response.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payment Connector - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/event.test.js new file mode 100644 index 00000000000..a6947db94c0 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/event.test.js @@ -0,0 +1,61 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/request.json new file mode 100644 index 00000000000..11c9749e958 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/request.json @@ -0,0 +1,47 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 10000, + "currency": "USD", + "confirm": true, + "amount_to_capture": 10000, + "business_country": "US", + "customer_id": "not_a_rick_roll", + "return_url": "https://www.google.com", + "payment_method": "card_redirect", + "payment_method_type": "card_redirect", + "payment_method_data": { + "card_redirect": { + "card_redirect": {} + } + }, + "routing": { + "type": "single", + "data": "prophetpay" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": ["{{baseUrl}}"], + "path": ["payments"] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/.event.meta.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..d0a02af7436 --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/event.test.js @@ -0,0 +1,61 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} diff --git a/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/request.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/request.json new file mode 100644 index 00000000000..ef0b213739d --- /dev/null +++ b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/request.json @@ -0,0 +1,22 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": ["{{baseUrl}}"], + "path": ["payments", ":id"], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json b/postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/response.json rename to postman/collection-dir/prophetpay/Flow Testcases/QuickStart/Payments - Retrieve/response.json diff --git a/postman/collection-dir/prophetpay/Health check/.meta.json b/postman/collection-dir/prophetpay/Health check/.meta.json new file mode 100644 index 00000000000..579f9fd541c --- /dev/null +++ b/postman/collection-dir/prophetpay/Health check/.meta.json @@ -0,0 +1,3 @@ +{ + "childrenOrder": ["New Request"] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/.event.meta.json b/postman/collection-dir/prophetpay/Health check/New Request/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/.event.meta.json rename to postman/collection-dir/prophetpay/Health check/New Request/.event.meta.json diff --git a/postman/collection-dir/prophetpay/Health check/New Request/event.test.js b/postman/collection-dir/prophetpay/Health check/New Request/event.test.js new file mode 100644 index 00000000000..b490b8be090 --- /dev/null +++ b/postman/collection-dir/prophetpay/Health check/New Request/event.test.js @@ -0,0 +1,4 @@ +// Validate status 2xx +pm.test("[POST]::/accounts - Status code is 2xx", function () { + pm.response.to.be.success; +}); diff --git a/postman/collection-dir/prophetpay/Health check/New Request/request.json b/postman/collection-dir/prophetpay/Health check/New Request/request.json new file mode 100644 index 00000000000..7ddcfbd2b72 --- /dev/null +++ b/postman/collection-dir/prophetpay/Health check/New Request/request.json @@ -0,0 +1,8 @@ +{ + "method": "GET", + "url": { + "raw": "{{baseUrl}}/health", + "host": ["{{baseUrl}}"], + "path": ["health"] + } +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/response.json b/postman/collection-dir/prophetpay/Health check/New Request/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/response.json rename to postman/collection-dir/prophetpay/Health check/New Request/response.json diff --git a/postman/collection-dir/prophetpay/event.prerequest.js b/postman/collection-dir/prophetpay/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/prophetpay/event.test.js b/postman/collection-dir/prophetpay/event.test.js new file mode 100644 index 00000000000..fb52caec30f --- /dev/null +++ b/postman/collection-dir/prophetpay/event.test.js @@ -0,0 +1,13 @@ +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log("[LOG]::payment_id - " + jsonData.payment_id); +} + +console.log("[LOG]::x-request-id - " + pm.response.headers.get("x-request-id")); diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/.meta.json index 62945fedcfa..bfeee020b5d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/.meta.json @@ -2,14 +2,18 @@ "childrenOrder": [ "Scenario1-Create payment with confirm true", "Scenario2-Create payment with confirm false", + "Scenario2a-Create payment with confirm false card holder name null", + "Scenario2b-Create payment with confirm false card holder name empty", "Scenario3-Create payment without PMD", "Scenario4-Create payment with Manual capture", + "Scenario4a-Create payment with manual_multiple capture", "Scenario5-Void the payment", "Scenario6-Create 3DS payment", "Scenario7-Create 3DS payment with confrm false", + "Scenario8-Create a failure card payment with confirm true", "Scenario9-Refund full payment", - "Scenario10-Partial refund", - "Scenario11-Create a mandate and recurring payment", + "Scenario9a-Partial refund", + "Scenario10-Create a mandate and recurring payment", "Scenario11-Refund recurring payment", "Scenario12-BNPL-klarna", "Scenario13-BNPL-afterpay", @@ -19,9 +23,13 @@ "Scenario17-Bank Redirect-eps", "Scenario18-Bank Redirect-giropay", "Scenario19-Bank Transfer-ach", - "Scenario19-Bank Debit-ach", - "Scenario22-Wallet-Wechatpay", + "Scenario20-Bank Debit-ach", + "Scenario21-Wallet-Wechatpay", "Scenario22- Update address and List Payment method", - "Scenario23- Update Amount" + "Scenario23- Update Amount", + "Scenario24-Add card flow", + "Scenario25-Don't Pass CVV for save card flow and verifysuccess payment", + "Scenario26-Save card payment with manual capture", + "Scenario27-Create payment without customer_id and with billing address and shipping address" ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json similarity index 97% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json index a5c9391cf74..3ffbe03a605 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json @@ -98,8 +98,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/request.json similarity index 86% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/response.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve-copy/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/request.json similarity index 96% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/request.json index 01f47678bea..613e9148f78 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/request.json @@ -61,8 +61,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Recurring Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json index 599c708ba73..9df18b5e886 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json @@ -86,8 +86,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/request.json index 304d0335058..fe8a73d4581 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Recurring Payments - Create/request.json @@ -61,8 +61,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/request.json index 5f4c58816d5..5e306df7a55 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Create Copy/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Refunds - Retrieve Copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/.event.meta.json deleted file mode 100644 index 220b1a6723d..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js", "event.prerequest.js"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json index 220b1a6723d..4ac527d834a 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js", "event.prerequest.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js index dc69bd52a50..f92fba3d044 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -63,26 +63,6 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "6540" for "amount" -if (jsonData?.amount) { - pm.test( - "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", - function () { - pm.expect(jsonData.amount).to.eql(6540); - }, - ); -} - -// Response body should have value "6540" for "amount_capturable" -if (jsonData?.amount) { - pm.test( - "[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 0'", - function () { - pm.expect(jsonData.amount_capturable).to.eql(0); - }, - ); -} - // Response body should have value "succeeded" for "status" if (jsonData?.status) { pm.test( @@ -92,12 +72,3 @@ if (jsonData?.status) { }, ); } - -// Response body should have "connector_transaction_id" -pm.test( - "[POST]::/payments - Content check if 'connector_transaction_id' exists", - function () { - pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be - .true; - }, -); diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json index ec45ef29bb6..540a2fa1946 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json @@ -38,13 +38,41 @@ } }, "raw_json_formatted": { - "client_secret": "{{client_secret}}" + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "Joseph Doe", + "card_cvc": "123" + } + }, + "client_secret": "{{client_secret}}", + "browser_info": { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + } } }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json index 0731450e6b2..4ac527d834a 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js index 55dc35b9128..0444324000a 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "requires_confirmation" for "status" +// Response body should have value "requires_payment_method" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'", + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", function () { - pm.expect(jsonData.status).to.eql("requires_confirmation"); + pm.expect(jsonData.status).to.eql("requires_payment_method"); }, ); } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json index 4105bd1a869..b28abd0c309 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -32,16 +32,6 @@ "description": "Its my first payment request", "authentication_type": "no_three_ds", "return_url": "https://duck.com", - "payment_method": "card", - "payment_method_data": { - "card": { - "card_number": "4242424242424242", - "card_exp_month": "10", - "card_exp_year": "25", - "card_holder_name": "joseph Doe", - "card_cvc": "123" - } - }, "billing": { "address": { "line1": "1467", @@ -51,7 +41,7 @@ "state": "California", "zip": "94122", "country": "US", - "first_name": "sundari" + "first_name": "PiX" } }, "shipping": { @@ -63,7 +53,7 @@ "state": "California", "zip": "94122", "country": "US", - "first_name": "sundari" + "first_name": "PiX" } }, "statement_descriptor_name": "joseph", @@ -72,17 +62,17 @@ "udf1": "value1", "new_customer": "true", "login_date": "2019-09-10T10:11:12Z" - }, - "routing": { - "type": "single", - "data": "stripe" } } }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js index 53f98b7f7f4..aced67dbfb7 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js @@ -69,23 +69,3 @@ if (jsonData?.status) { }, ); } - -// Response body should have value "6540" for "amount" -if (jsonData?.amount) { - pm.test( - "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", - function () { - pm.expect(jsonData.amount).to.eql(6540); - }, - ); -} - -// Response body should have value "6540" for "amount_capturable" -if (jsonData?.amount) { - pm.test( - "[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 0'", - function () { - pm.expect(jsonData.amount_capturable).to.eql(0); - }, - ); -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/.meta.json new file mode 100644 index 00000000000..60051ecca22 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/request.json similarity index 95% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/request.json index 9612b490987..eda0801c9cc 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/request.json @@ -23,7 +23,9 @@ "confirm": true, "business_label": "default", "capture_method": "automatic", - "connector": ["stripe"], + "connector": [ + "stripe" + ], "customer_id": "klarna", "capture_on": "2022-09-10T10:11:12Z", "authentication_type": "three_ds", @@ -83,8 +85,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario20-Bank Debit-ach/Payments - Retrieve/response.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/.meta.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/request.json similarity index 92% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/request.json index 9189e4dd852..42d3653ad96 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/request.json @@ -50,8 +50,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Confirm/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/request.json similarity index 95% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/request.json index 731eeaf1400..daffd2ab9ec 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/request.json @@ -49,8 +49,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario21-Wallet-Wechatpay/Payments - Retrieve/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant-copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant-copy/request.json index 060c693c7e1..fed600e09cd 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant-copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant-copy/request.json @@ -32,15 +32,6 @@ "disabled": true } ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", "host": [ diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant/request.json index 060c693c7e1..fed600e09cd 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/List Payment Methods for a Merchant/request.json @@ -32,15 +32,6 @@ "disabled": true } ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", "host": [ diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/.event.meta.json deleted file mode 100644 index 220b1a6723d..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Confirm/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js", "event.prerequest.js"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json deleted file mode 100644 index 69b505c6d86..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "childrenOrder": ["Payments - Create", "Payments - Retrieve"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/.meta.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js new file mode 100644 index 00000000000..f92fba3d044 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js @@ -0,0 +1,74 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json new file mode 100644 index 00000000000..604ac54144d --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json @@ -0,0 +1,85 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": null, + "card_cvc": "123" + } + }, + "client_secret": "{{client_secret}}", + "browser_info": { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/request.json new file mode 100644 index 00000000000..b28abd0c309 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/request.json @@ -0,0 +1,78 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": 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", + "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" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/response.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Create/response.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..aced67dbfb7 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/.meta.json new file mode 100644 index 00000000000..57d3f8e2bc7 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js new file mode 100644 index 00000000000..9147db9461b --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js @@ -0,0 +1,74 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json new file mode 100644 index 00000000000..371ef616fe4 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json @@ -0,0 +1,85 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "", + "card_cvc": "123" + } + }, + "client_secret": "{{client_secret}}", + "browser_info": { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..4ac527d834a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/event.prerequest.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/event.test.js new file mode 100644 index 00000000000..0444324000a --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/request.json new file mode 100644 index 00000000000..b28abd0c309 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/request.json @@ -0,0 +1,78 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": 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", + "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" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..3a120471358 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/request.json deleted file mode 100644 index 6cd4b7d96c5..00000000000 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/request.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" -} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/request.json new file mode 100644 index 00000000000..8975575ca40 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/request.json @@ -0,0 +1,45 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount_to_capture": 6000, + "statement_descriptor_name": "Joseph", + "statement_descriptor_suffix": "JS" + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/capture", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To capture the funds for an uncaptured payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Capture/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/event.test.js new file mode 100644 index 00000000000..d683186aa00 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_capture" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_capture'", + function () { + pm.expect(jsonData.status).to.eql("requires_capture"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json new file mode 100644 index 00000000000..0fbd6a4dcdd --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json @@ -0,0 +1,92 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "capture_method": "manual", + "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", + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "sundari" + } + }, + "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", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/.meta.json new file mode 100644 index 00000000000..60051ecca22 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/.meta.json @@ -0,0 +1,6 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/request.json similarity index 97% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/request.json index 6542d21542d..731b249f2aa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/request.json @@ -87,8 +87,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json similarity index 96% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json index 2363c62ff27..b5f464abc14 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json @@ -81,8 +81,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve-copy/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve-copy/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/request.json similarity index 90% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/request.json index caed7818578..b56057fad5d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create-copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create-copy/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/request.json new file mode 100644 index 00000000000..d18aaf8befd --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/request.json @@ -0,0 +1,42 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_id": "{{payment_id}}", + "amount": 540, + "reason": "Customer returned product", + "refund_type": "instant", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/request.json similarity index 84% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/request.json rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve-copy/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve-copy/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Retrieve/event.test.js rename to postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/request.json new file mode 100644 index 00000000000..6c28619e856 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/request.json @@ -0,0 +1,27 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/refunds/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" + } + ] + }, + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Refunds - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json index 474b4d1e1ea..291a5bf84b5 100644 --- a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payment Connector - Create/request.json @@ -211,7 +211,12 @@ "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true, - "card_networks": ["Visa", "Mastercard"] + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"] } ] }, @@ -224,7 +229,12 @@ "maximum_amount": 68607706, "recurring_enabled": true, "installment_payment_enabled": true, - "card_networks": ["Visa", "Mastercard"] + "card_networks": ["AmericanExpress", + "Discover", + "Interac", + "JCB", + "Mastercard", + "Visa", "DinersClub","UnionPay","RuPay"] } ] }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/.meta.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/.meta.json new file mode 100644 index 00000000000..7dd4c0a0c21 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/.meta.json @@ -0,0 +1,8 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Capture", + "Payments - Retrieve", + "Refunds - Create" + ] +} \ No newline at end of file diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/.event.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/.event.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/event.test.js new file mode 100644 index 00000000000..f560d84ea73 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/event.test.js @@ -0,0 +1,94 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/capture - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/capture - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/capture - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'", + function () { + pm.expect(jsonData.status).to.eql("partially_captured"); + }, + ); +} + +// Response body should have value "6540" for "amount" +if (jsonData?.amount) { + pm.test( + "[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'", + function () { + pm.expect(jsonData.amount).to.eql(6540); + }, + ); +} + +// Response body should have value "6000" for "amount_received" +if (jsonData?.amount_received) { + pm.test( + "[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'", + function () { + pm.expect(jsonData.amount_received).to.eql(6000); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/request.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Capture/request.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/request.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/response.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Capture/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/event.test.js new file mode 100644 index 00000000000..d683186aa00 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_capture" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_capture'", + function () { + pm.expect(jsonData.status).to.eql("requires_capture"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json similarity index 98% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json index 0619498e38c..90b6e3bd038 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with manual_multiple capture/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json @@ -23,7 +23,6 @@ "confirm": true, "capture_method": "manual", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 6540, "customer_id": "StripeCustomer", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Recurring Payments - Create/.event.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..92af2088cd9 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "Succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id - Content check if value for 'status' matches 'partially_captured'", + function () { + pm.expect(jsonData.status).to.eql("partially_captured"); + }, + ); +} diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Payments - Retrieve/request.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/request.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/response.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/.event.meta.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario19-Bank Debit-ach/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/.event.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/event.test.js b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/event.test.js new file mode 100644 index 00000000000..07721f97af3 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/event.test.js @@ -0,0 +1,58 @@ +// Validate status 4xx +pm.test("[POST]::/refunds - Status code is 4xx", function () { + pm.response.to.be.error; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/refunds - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id +if (jsonData?.refund_id) { + pm.collectionVariables.set("refund_id", jsonData.refund_id); + console.log( + "- use {{refund_id}} as collection variable for value", + jsonData.refund_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.", + ); +} + +// Response body should have "error" +pm.test( + "[POST]::/payments/:id/confirm - Content check if 'error' exists", + function () { + pm.expect(typeof jsonData.error !== "undefined").to.be.true; + }, +); + +// Response body should have value "invalid_request" for "error type" +if (jsonData?.error?.type) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error.type' matches 'invalid_request'", + function () { + pm.expect(jsonData.error.type).to.eql("invalid_request"); + }, + ); +} + +// Response body should have value "The refund amount exceeds the amount captured" for "error message" +if (jsonData?.error?.type) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'", + function () { + pm.expect(jsonData.error.message).to.eql("The refund amount exceeds the amount captured"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/request.json similarity index 97% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/request.json rename to postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/request.json index 9fe125ce8ea..5f4c58816d5 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/request.json @@ -19,7 +19,7 @@ }, "raw_json_formatted": { "payment_id": "{{payment_id}}", - "amount": 540, + "amount": 6540, "reason": "Customer returned product", "refund_type": "instant", "metadata": { diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/response.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Refunds - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - List/request.json b/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - List/request.json index 841485a0a04..ed2324e0308 100644 --- a/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - List/request.json +++ b/postman/collection-dir/stripe/MerchantAccounts/Merchant Account - List/request.json @@ -27,14 +27,18 @@ } ], "url": { - "raw": "{{baseUrl}}/accounts/list", - "host": ["{{baseUrl}}"], - "path": ["accounts", "list"], + "raw": "{{baseUrl}}/accounts/list?organization_id={{organization_id}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "accounts", + "list" + ], "query": [ { "key": "organization_id", - "value": "{{organization_id}}", - "disabled": false + "value": "{{organization_id}}" } ], "variable": [ diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/.meta.json index 6014e253b0a..949d82095ec 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/.meta.json @@ -2,11 +2,13 @@ "childrenOrder": [ "Scenario1-Create payment with confirm true", "Scenario2-Create payment with confirm false", - "Scenario3-Create payment without PMD", - "Scenario4-Create 3DS payment", - "Scenario5-Create 3DS payment with confrm false", - "Scenario6-Refund full payment", + "Scenario3-Create payment with confirm false card holder name null", + "Scenario3-Create payment with confirm false card holder name empty", + "Scenario4-Create payment without PMD", + "Scenario5-Create 3DS payment", + "Scenario6-Create 3DS payment with confrm false", + "Scenario7-Refund full payment", "Scenario8-Bank Redirect-Ideal", - "Scenario11-Bank Redirect-giropay" + "Scenario9-Bank Redirect-giropay" ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json index 0731450e6b2..4ac527d834a 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json index d999cf2d649..540a2fa1946 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/request.json @@ -38,6 +38,16 @@ } }, "raw_json_formatted": { + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "Joseph Doe", + "card_cvc": "123" + } + }, "client_secret": "{{client_secret}}", "browser_info": { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", @@ -55,8 +65,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json index 0731450e6b2..4ac527d834a 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js index 55dc35b9128..0444324000a 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "requires_confirmation" for "status" +// Response body should have value "requires_payment_method" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'", + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", function () { - pm.expect(jsonData.status).to.eql("requires_confirmation"); + pm.expect(jsonData.status).to.eql("requires_payment_method"); }, ); } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json index 119353069bd..b28abd0c309 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -32,16 +32,6 @@ "description": "Its my first payment request", "authentication_type": "no_three_ds", "return_url": "https://duck.com", - "payment_method": "card", - "payment_method_data": { - "card": { - "card_number": "4242424242424242", - "card_exp_month": "10", - "card_exp_year": "25", - "card_holder_name": "joseph Doe", - "card_cvc": "123" - } - }, "billing": { "address": { "line1": "1467", @@ -77,8 +67,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/.meta.json new file mode 100644 index 00000000000..57d3f8e2bc7 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js new file mode 100644 index 00000000000..9147db9461b --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/event.test.js @@ -0,0 +1,74 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/request.json new file mode 100644 index 00000000000..371ef616fe4 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/request.json @@ -0,0 +1,85 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "", + "card_cvc": "123" + } + }, + "client_secret": "{{client_secret}}", + "browser_info": { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/event.test.js new file mode 100644 index 00000000000..0444324000a --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/request.json new file mode 100644 index 00000000000..b28abd0c309 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/request.json @@ -0,0 +1,78 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": 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", + "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" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..3a120471358 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} \ No newline at end of file diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name empty/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/.meta.json new file mode 100644 index 00000000000..57d3f8e2bc7 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js new file mode 100644 index 00000000000..f92fba3d044 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/event.test.js @@ -0,0 +1,74 @@ +// Validate status 2xx +pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/payments/:id/confirm - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Validate if response has JSON Body +pm.test("[POST]::/payments/:id/confirm - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/request.json new file mode 100644 index 00000000000..604ac54144d --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/request.json @@ -0,0 +1,85 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "payment_method": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": null, + "card_cvc": "123" + } + }, + "client_secret": "{{client_secret}}", + "browser_info": { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/event.test.js new file mode 100644 index 00000000000..0444324000a --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/request.json new file mode 100644 index 00000000000..b28abd0c309 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/request.json @@ -0,0 +1,78 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": 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", + "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" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..aced67dbfb7 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/request.json new file mode 100644 index 00000000000..b9ebc1be4aa --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/request.json @@ -0,0 +1,33 @@ +{ + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name null/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json index d30e00868a5..8ff62125d1a 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json @@ -65,8 +65,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json index e3b1e235042..b28abd0c309 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Create/request.json @@ -67,8 +67,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/.meta.json index 69b505c6d86..60051ecca22 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/.meta.json @@ -1,3 +1,6 @@ { - "childrenOrder": ["Payments - Create", "Payments - Retrieve"] + "childrenOrder": [ + "Payments - Create", + "Payments - Retrieve" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/request.json index 7dd4aac02ce..80661beddf2 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Create/request.json @@ -101,8 +101,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/request.json index 61fcdaec2d2..6f4d51c5945 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario4-Create 3DS payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/.event.meta.json index 0731450e6b2..4ac527d834a 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/.event.meta.json @@ -1,3 +1,6 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/event.prerequest.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/event.prerequest.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/request.json index 9fef5309c3a..fb1c8124ca7 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Confirm/request.json @@ -46,7 +46,7 @@ "card_number": "5200000000000015", "card_exp_month": "03", "card_exp_year": "2030", - "card_holder_name": "", + "card_holder_name": "John Doe", "card_cvc": "737", "card_issuer": "", "card_network": "Visa" @@ -68,8 +68,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/request.json index 3b4c8f74e7f..0eb2d9f30cf 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Create/request.json @@ -78,8 +78,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/request.json index 61fcdaec2d2..6f4d51c5945 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario5-Create 3DS payment with confrm false/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/request.json index 94473ddb1ec..c3dddef3a2d 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Create/request.json @@ -100,8 +100,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/request.json index 6cd4b7d96c5..b9ebc1be4aa 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/request.json index 5f4c58816d5..5e306df7a55 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Create/request.json @@ -31,8 +31,12 @@ }, "url": { "raw": "{{baseUrl}}/refunds", - "host": ["{{baseUrl}}"], - "path": ["refunds"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] }, "description": "To create a refund against an already processed payment" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/.event.meta.json index 0731450e6b2..688c85746ef 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/.event.meta.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/.event.meta.json @@ -1,3 +1,5 @@ { - "eventOrder": ["event.test.js"] + "eventOrder": [ + "event.test.js" + ] } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/request.json index c4271891fbf..6c28619e856 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario6-Refund full payment/Refunds - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/refunds/:id", - "host": ["{{baseUrl}}"], - "path": ["refunds", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/.meta.json new file mode 100644 index 00000000000..57d3f8e2bc7 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json similarity index 93% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json index 10b88b51748..c4c248b3bdd 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json @@ -57,8 +57,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/event.test.js new file mode 100644 index 00000000000..0444324000a --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json similarity index 97% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json index 0b0c56d2660..87d07a6016b 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json @@ -81,8 +81,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/request.json similarity index 86% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/request.json index 61fcdaec2d2..6f4d51c5945 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Confirm/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/.meta.json new file mode 100644 index 00000000000..57d3f8e2bc7 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "Payments - Create", + "Payments - Confirm", + "Payments - Retrieve" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/request.json similarity index 93% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/request.json index caf10ddd35d..3f8a95e59bb 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-giropay/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/request.json @@ -57,8 +57,14 @@ }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], "variable": [ { "key": "id", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Confirm/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/event.test.js new file mode 100644 index 00000000000..0444324000a --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_payment_method" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", + function () { + pm.expect(jsonData.status).to.eql("requires_payment_method"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/request.json similarity index 97% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/request.json index 0b0c56d2660..87d07a6016b 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/request.json @@ -81,8 +81,12 @@ }, "url": { "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] }, "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json new file mode 100644 index 00000000000..688c85746ef --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/event.test.js new file mode 100644 index 00000000000..9053ddab13b --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/event.test.js @@ -0,0 +1,71 @@ +// Validate status 2xx +pm.test("[GET]::/payments/:id - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[GET]::/payments/:id - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "requires_customer_action" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'", + function () { + pm.expect(jsonData.status).to.eql("requires_customer_action"); + }, + ); +} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/request.json similarity index 86% rename from postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/request.json index 61fcdaec2d2..6f4d51c5945 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-Ideal/Payments - Retrieve/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/request.json @@ -8,8 +8,13 @@ ], "url": { "raw": "{{baseUrl}}/payments/:id", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], "query": [ { "key": "force_sync", diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/response.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario8-Bank Redirect-giropay/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/.meta.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/.meta.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/.meta.json diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Create/.event.meta.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/request.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22-Wallet-Wechatpay/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/request.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Create a mandate and recurring payment/Payments - Retrieve-copy/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/request.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/response.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Payments - Retrieve/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Create a failure card payment with confirm true/Payments - Create/.event.meta.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/.event.meta.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/event.test.js b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/event.test.js similarity index 100% rename from postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/event.test.js rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/event.test.js diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/request.json similarity index 100% rename from postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Partial refund/Refunds - Create/request.json rename to postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/request.json diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/response.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario5-Refund for unsuccessful payment/Refunds - Create/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/request.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/request.json deleted file mode 100644 index 6cd4b7d96c5..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Retrieve/request.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id"], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" -} diff --git a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/.event.meta.json b/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/trustpay/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Refunds - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-json/adyen_uk.postman_collection.json b/postman/collection-json/adyen_uk.postman_collection.json index 04a7e39f15e..26963aa8abb 100644 --- a/postman/collection-json/adyen_uk.postman_collection.json +++ b/postman/collection-json/adyen_uk.postman_collection.json @@ -472,7 +472,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"adyen\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\",\"api_secret\":\"{{connector_api_secret}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"gift_card\",\"payment_method_types\":[{\"payment_method_type\":\"givex\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdKakNDQlE2Z0F3SUJBZ0lRRENzRmFrVkNLU01uc2JacTc1YTI0ekFOQmdrcWhraUc5dzBCQVFzRkFEQjEKTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRApaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFTE1Ba0dBMVVFQ3d3Q1J6TXhFekFSQmdOVkJBb01Da0Z3CmNHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeU1USXdPREE1TVRJeE1Wb1hEVEkxTURFd05qQTUKTVRJeE1Gb3dnYWd4SmpBa0Jnb0praWFKay9Jc1pBRUJEQlp0WlhKamFHRnVkQzVqYjIwdVlXUjVaVzR1YzJGdQpNVHN3T1FZRFZRUUREREpCY0hCc1pTQlFZWGtnVFdWeVkyaGhiblFnU1dSbGJuUnBkSGs2YldWeVkyaGhiblF1ClkyOXRMbUZrZVdWdUxuTmhiakVUTUJFR0ExVUVDd3dLV1UwNVZUY3pXakpLVFRFc01Db0dBMVVFQ2d3alNsVlQKVUVGWklGUkZRMGhPVDB4UFIwbEZVeUJRVWtsV1FWUkZJRXhKVFVsVVJVUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQgpBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRDhIUy81ZmJZNVJLaElYU3pySEpoeTVrNmY0YUdMaEltYklLaXFYRUlUCnVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYzV5eGE0cHg5eHlmQlVIejhzeU9pMjdYNVZaVG8KTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVZlhQWHBjdjFqVVRyRCtlc1RJTFZUb1FUTmhDcwplQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWNaWC9vWTB1R040VWd4c0JYWHdZM0dKbTFSQ3B1CjM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSEhNMGpEQ2lncVU1RktwL1pBbHdzYmg1WVZOU00KWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nV1Y0Z0hUNmhBZ01CQUFHamdnSjhNSUlDZURBTQpCZ05WSFJNQkFmOEVBakFBTUI4R0ExVWRJd1FZTUJhQUZBbit3QldRK2E5a0NwSVN1U1lvWXd5WDdLZXlNSEFHCkNDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnYKYlM5M2QyUnlaek11WkdWeU1ERUdDQ3NHQVFVRkJ6QUJoaVZvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdgpiMk56Y0RBekxYZDNaSEpuTXpBNU1JSUJMUVlEVlIwZ0JJSUJKRENDQVNBd2dnRWNCZ2txaGtpRzkyTmtCUUV3CmdnRU5NSUhSQmdnckJnRUZCUWNDQWpDQnhBeUJ3VkpsYkdsaGJtTmxJRzl1SUhSb2FYTWdRMlZ5ZEdsbWFXTmgKZEdVZ1lua2dZVzU1SUhCaGNuUjVJRzkwYUdWeUlIUm9ZVzRnUVhCd2JHVWdhWE1nY0hKdmFHbGlhWFJsWkM0ZwpVbVZtWlhJZ2RHOGdkR2hsSUdGd2NHeHBZMkZpYkdVZ2MzUmhibVJoY21RZ2RHVnliWE1nWVc1a0lHTnZibVJwCmRHbHZibk1nYjJZZ2RYTmxMQ0JqWlhKMGFXWnBZMkYwWlNCd2IyeHBZM2tnWVc1a0lHTmxjblJwWm1sallYUnAKYjI0Z2NISmhZM1JwWTJVZ2MzUmhkR1Z0Wlc1MGN5NHdOd1lJS3dZQkJRVUhBZ0VXSzJoMGRIQnpPaTh2ZDNkMwpMbUZ3Y0d4bExtTnZiUzlqWlhKMGFXWnBZMkYwWldGMWRHaHZjbWwwZVM4d0V3WURWUjBsQkF3d0NnWUlLd1lCCkJRVUhBd0l3SFFZRFZSME9CQllFRk5RSysxcUNHbDRTQ1p6SzFSUmpnb05nM0hmdk1BNEdBMVVkRHdFQi93UUUKQXdJSGdEQlBCZ2txaGtpRzkyTmtCaUFFUWd4QVFVUkNRemxDTmtGRE5USkVRems0TnpCRk5qYzJNVFpFUkRJdwpPVUkwTWtReE1UVXlSVVpFTURVeFFVRXhRekV6T0ROR00wUkROa1V5TkVNelFqRkVSVEFQQmdrcWhraUc5Mk5rCkJpNEVBZ1VBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBSFR6NTU2RUs5VVp6R0RVd2cvcmFibmYrUXFSYkgKcllVS0ZNcWQwUDhFTHZGMmYrTzN0ZXlDWHNBckF4TmVMY2hRSGVTNUFJOHd2azdMQ0xLUmJCdWJQQy9NVmtBKwpCZ3h5STg2ejJOVUNDWml4QVM1d2JFQWJYOStVMFp2RHp5Y01BbUNrdVVHZjNwWXR5TDNDaEplSGRvOEwwdmdvCnJQWElUSzc4ZjQzenNzYjBTNE5xbTE0eS9LNCs1ZkUzcFUxdEJqME5tUmdKUVJLRnB6MENpV2RPd1BRTk5BYUMKYUNNU2NiYXdwUTBjWEhaZDJWVjNtem4xdUlITEVtaU5GTWVxeEprbjZhUXFFQnNndDUzaUFxcmZMNjEzWStScAppd0tENXVmeU0wYzBweTYyZmkvWEwwS2c4ajEwWU1VdWJpd2dHajAzZThQWTB6bWUvcGZCZ3p6VQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\",\"display_name\":\"applepay\",\"certificate_keys\":\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRDhIUy81ZmJZNVJLaEkKWFN6ckhKaHk1azZmNGFHTGhJbWJJS2lxWEVJVHVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYwo1eXhhNHB4OXh5ZkJVSHo4c3lPaTI3WDVWWlRvTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVCmZYUFhwY3YxalVUckQrZXNUSUxWVG9RVE5oQ3NlQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWMKWlgvb1kwdUdONFVneHNCWFh3WTNHSm0xUkNwdTM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSApITTBqRENpZ3FVNUZLcC9aQWx3c2JoNVlWTlNNWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nCldWNGdIVDZoQWdNQkFBRUNnZ0VBZFNaRzVhTFJxdmpKVFo3bVFYWHZMT3p4dWY5SlpxQTJwUHZmQkJLTXJjZC8KL2RDZXpGSGRhZ1VvWXNUQjRXekluaVVjL2Z3bDJTUzJyREFMZjB2dnRjNTJIYkQ5OHhwMnc3VmVjTGFnMCtuWAo2dUJaSEZCS3FWNU1LZ1l6YUpVMTdqaDM2VEV3dTFnbmdlZnRqVlpBV1NERTFvbDBlSzZ3Mk5kOExjVWdxRkxUCjVHYUlBV01nd0NKL3pzQmwydUV1Y0Q4S21WL1Z2MkVCQVJLWGZtci92UU1NelZrNkhhalprVGZqbWY2cWFVQVMKQWlFblROcHBic2ZrdTk2VGdIa2owWm10VWc0SFkzSU9qWFlpaGJsSjJzQ1JjS3p6cXkxa3B3WlpHcHo1NXEzbgphSXEwenJ3RjlpTUZubEhCa04yK3FjSnhzcDNTalhRdFRLTTY4WHRrVlFLQmdRRC8wemtCVlExR2Q1U0Mzb2czCnM3QWRCZ243dnVMdUZHZFFZY3c0OUppRGw1a1BZRXlKdGQvTVpNOEpFdk1nbVVTeUZmczNZcGtmQ2VGbUp0S3QKMnNNNEdCRWxqTVBQNjI3Q0QrV3c4L3JpWmlOZEg3OUhPRjRneTRGbjBycDNqanlLSWF1OHJISDQwRUUvSkVyOQpxWFQ1SGdWMmJQOGhMcW5sSjFmSDhpY2Zkd0tCZ1FEOFNWQ3ZDV2txQkh2SzE5ZDVTNlArdm5hcXhnTWo0U0srCnJ6L1I1c3pTaW5lS045VEhyeVkxYUZJbVFJZjJYOUdaQXBocUhrckxtQ3BIcURHOWQ3WDVQdUxxQzhmc09kVTYKRzhWaFRXeXdaSVNjdGRSYkk5S2xKUFk2V2ZDQjk0ODNVaDJGbW1xV2JuNWcwZUJxZWZzamVVdEtHekNRaGJDYworR1dBREVRSXB3S0JnUURmaWYvN3pBZm5sVUh1QU9saVV0OEczV29IMGtxVTRydE1IOGpGMCtVWXgzVDFYSjVFCmp1blp2aFN5eHg0dlUvNFU1dVEzQnk3cFVrYmtiZlFWK2x3dlBjaHQyVXlZK0E0MkFKSWlSMjdvT1h1Wk9jNTQKT3liMDNSNWNUR1NuWjJBN0N5VDNubStRak5rV2hXNEpyUE1MWTFJK293dGtRVlF2YW10bnlZNnFEUUtCZ0ZYWgpLT0IzSmxjSzhZa0R5Nm5WeUhkZUhvbGNHaU55YjkxTlN6MUUrWHZIYklnWEdZdmRtUFhoaXRyRGFNQzR1RjBGCjJoRjZQMTlxWnpDOUZqZnY3WGRrSTlrYXF5eENQY0dwUTVBcHhZdDhtUGV1bEJWemFqR1NFMHVsNFVhSWxDNXgKL2VQQnVQVjVvZjJXVFhST0Q5eHhZT0pWd0QvZGprekw1ZFlkMW1UUEFvR0JBTWVwY3diVEphZ3BoZk5zOHY0WAprclNoWXplbVpxY2EwQzRFc2QwNGYwTUxHSlVNS3Zpck4zN0g1OUFjT2IvNWtZcTU5WFRwRmJPWjdmYlpHdDZnCkxnM2hWSHRacElOVGJ5Ni9GOTBUZ09Za3RxUnhNVmc3UFBxbjFqdEFiVU15eVpVZFdHcFNNMmI0bXQ5dGhlUDEKblhMR09NWUtnS2JYbjZXWWN5K2U5eW9ICi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KCg==\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.adyen.san\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"adyen\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\",\"api_secret\":\"{{connector_api_secret}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"pay_bright\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"walley\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mobile_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"ali_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"mb_way\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"gift_card\",\"payment_method_types\":[{\"payment_method_type\":\"givex\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"giropay\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"blik\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"trustly\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_czech_republic\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_finland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_poland\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"online_banking_slovakia\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bancontact_card\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\"}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdKakNDQlE2Z0F3SUJBZ0lRRENzRmFrVkNLU01uc2JacTc1YTI0ekFOQmdrcWhraUc5dzBCQVFzRkFEQjEKTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRApaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFTE1Ba0dBMVVFQ3d3Q1J6TXhFekFSQmdOVkJBb01Da0Z3CmNHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeU1USXdPREE1TVRJeE1Wb1hEVEkxTURFd05qQTUKTVRJeE1Gb3dnYWd4SmpBa0Jnb0praWFKay9Jc1pBRUJEQlp0WlhKamFHRnVkQzVqYjIwdVlXUjVaVzR1YzJGdQpNVHN3T1FZRFZRUUREREpCY0hCc1pTQlFZWGtnVFdWeVkyaGhiblFnU1dSbGJuUnBkSGs2YldWeVkyaGhiblF1ClkyOXRMbUZrZVdWdUxuTmhiakVUTUJFR0ExVUVDd3dLV1UwNVZUY3pXakpLVFRFc01Db0dBMVVFQ2d3alNsVlQKVUVGWklGUkZRMGhPVDB4UFIwbEZVeUJRVWtsV1FWUkZJRXhKVFVsVVJVUXdnZ0VpTUEwR0NTcUdTSWIzRFFFQgpBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRDhIUy81ZmJZNVJLaElYU3pySEpoeTVrNmY0YUdMaEltYklLaXFYRUlUCnVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYzV5eGE0cHg5eHlmQlVIejhzeU9pMjdYNVZaVG8KTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVZlhQWHBjdjFqVVRyRCtlc1RJTFZUb1FUTmhDcwplQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWNaWC9vWTB1R040VWd4c0JYWHdZM0dKbTFSQ3B1CjM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSEhNMGpEQ2lncVU1RktwL1pBbHdzYmg1WVZOU00KWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nV1Y0Z0hUNmhBZ01CQUFHamdnSjhNSUlDZURBTQpCZ05WSFJNQkFmOEVBakFBTUI4R0ExVWRJd1FZTUJhQUZBbit3QldRK2E5a0NwSVN1U1lvWXd5WDdLZXlNSEFHCkNDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnYKYlM5M2QyUnlaek11WkdWeU1ERUdDQ3NHQVFVRkJ6QUJoaVZvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdgpiMk56Y0RBekxYZDNaSEpuTXpBNU1JSUJMUVlEVlIwZ0JJSUJKRENDQVNBd2dnRWNCZ2txaGtpRzkyTmtCUUV3CmdnRU5NSUhSQmdnckJnRUZCUWNDQWpDQnhBeUJ3VkpsYkdsaGJtTmxJRzl1SUhSb2FYTWdRMlZ5ZEdsbWFXTmgKZEdVZ1lua2dZVzU1SUhCaGNuUjVJRzkwYUdWeUlIUm9ZVzRnUVhCd2JHVWdhWE1nY0hKdmFHbGlhWFJsWkM0ZwpVbVZtWlhJZ2RHOGdkR2hsSUdGd2NHeHBZMkZpYkdVZ2MzUmhibVJoY21RZ2RHVnliWE1nWVc1a0lHTnZibVJwCmRHbHZibk1nYjJZZ2RYTmxMQ0JqWlhKMGFXWnBZMkYwWlNCd2IyeHBZM2tnWVc1a0lHTmxjblJwWm1sallYUnAKYjI0Z2NISmhZM1JwWTJVZ2MzUmhkR1Z0Wlc1MGN5NHdOd1lJS3dZQkJRVUhBZ0VXSzJoMGRIQnpPaTh2ZDNkMwpMbUZ3Y0d4bExtTnZiUzlqWlhKMGFXWnBZMkYwWldGMWRHaHZjbWwwZVM4d0V3WURWUjBsQkF3d0NnWUlLd1lCCkJRVUhBd0l3SFFZRFZSME9CQllFRk5RSysxcUNHbDRTQ1p6SzFSUmpnb05nM0hmdk1BNEdBMVVkRHdFQi93UUUKQXdJSGdEQlBCZ2txaGtpRzkyTmtCaUFFUWd4QVFVUkNRemxDTmtGRE5USkVRems0TnpCRk5qYzJNVFpFUkRJdwpPVUkwTWtReE1UVXlSVVpFTURVeFFVRXhRekV6T0ROR00wUkROa1V5TkVNelFqRkVSVEFQQmdrcWhraUc5Mk5rCkJpNEVBZ1VBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBSFR6NTU2RUs5VVp6R0RVd2cvcmFibmYrUXFSYkgKcllVS0ZNcWQwUDhFTHZGMmYrTzN0ZXlDWHNBckF4TmVMY2hRSGVTNUFJOHd2azdMQ0xLUmJCdWJQQy9NVmtBKwpCZ3h5STg2ejJOVUNDWml4QVM1d2JFQWJYOStVMFp2RHp5Y01BbUNrdVVHZjNwWXR5TDNDaEplSGRvOEwwdmdvCnJQWElUSzc4ZjQzenNzYjBTNE5xbTE0eS9LNCs1ZkUzcFUxdEJqME5tUmdKUVJLRnB6MENpV2RPd1BRTk5BYUMKYUNNU2NiYXdwUTBjWEhaZDJWVjNtem4xdUlITEVtaU5GTWVxeEprbjZhUXFFQnNndDUzaUFxcmZMNjEzWStScAppd0tENXVmeU0wYzBweTYyZmkvWEwwS2c4ajEwWU1VdWJpd2dHajAzZThQWTB6bWUvcGZCZ3p6VQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\",\"display_name\":\"applepay\",\"certificate_keys\":\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRDhIUy81ZmJZNVJLaEkKWFN6ckhKaHk1azZmNGFHTGhJbWJJS2lxWEVJVHVSQ2RHcGcyMExZM1VhTlBlYXZXTVRIUTBpK3d1RzlZWFVhYwo1eXhhNHB4OXh5ZkJVSHo4c3lPaTI3WDVWWlRvTlFhd2F6dGM5aGpZc1B2K0s2UW9oaWRTQWZ3cDhMdThkQ0lVCmZYUFhwY3YxalVUckQrZXNUSUxWVG9RVE5oQ3NlQlJtUS9nK05WdTB5c3BqeUYxU2l6VG9BK1BML3NrMlJEYWMKWlgvb1kwdUdONFVneHNCWFh3WTNHSm0xUkNwdTM0Y2d0UC9kaHNBM1Ixb1VOb0gyQkZBSm9xK3pyUnl3U1RCSApITTBqRENpZ3FVNUZLcC9aQWx3c2JoNVlWTlNNWksrQ0pTK1BPTzlVNGVkeHJmTGlBVkhnQTgzRG43Z2U4K29nCldWNGdIVDZoQWdNQkFBRUNnZ0VBZFNaRzVhTFJxdmpKVFo3bVFYWHZMT3p4dWY5SlpxQTJwUHZmQkJLTXJjZC8KL2RDZXpGSGRhZ1VvWXNUQjRXekluaVVjL2Z3bDJTUzJyREFMZjB2dnRjNTJIYkQ5OHhwMnc3VmVjTGFnMCtuWAo2dUJaSEZCS3FWNU1LZ1l6YUpVMTdqaDM2VEV3dTFnbmdlZnRqVlpBV1NERTFvbDBlSzZ3Mk5kOExjVWdxRkxUCjVHYUlBV01nd0NKL3pzQmwydUV1Y0Q4S21WL1Z2MkVCQVJLWGZtci92UU1NelZrNkhhalprVGZqbWY2cWFVQVMKQWlFblROcHBic2ZrdTk2VGdIa2owWm10VWc0SFkzSU9qWFlpaGJsSjJzQ1JjS3p6cXkxa3B3WlpHcHo1NXEzbgphSXEwenJ3RjlpTUZubEhCa04yK3FjSnhzcDNTalhRdFRLTTY4WHRrVlFLQmdRRC8wemtCVlExR2Q1U0Mzb2czCnM3QWRCZ243dnVMdUZHZFFZY3c0OUppRGw1a1BZRXlKdGQvTVpNOEpFdk1nbVVTeUZmczNZcGtmQ2VGbUp0S3QKMnNNNEdCRWxqTVBQNjI3Q0QrV3c4L3JpWmlOZEg3OUhPRjRneTRGbjBycDNqanlLSWF1OHJISDQwRUUvSkVyOQpxWFQ1SGdWMmJQOGhMcW5sSjFmSDhpY2Zkd0tCZ1FEOFNWQ3ZDV2txQkh2SzE5ZDVTNlArdm5hcXhnTWo0U0srCnJ6L1I1c3pTaW5lS045VEhyeVkxYUZJbVFJZjJYOUdaQXBocUhrckxtQ3BIcURHOWQ3WDVQdUxxQzhmc09kVTYKRzhWaFRXeXdaSVNjdGRSYkk5S2xKUFk2V2ZDQjk0ODNVaDJGbW1xV2JuNWcwZUJxZWZzamVVdEtHekNRaGJDYworR1dBREVRSXB3S0JnUURmaWYvN3pBZm5sVUh1QU9saVV0OEczV29IMGtxVTRydE1IOGpGMCtVWXgzVDFYSjVFCmp1blp2aFN5eHg0dlUvNFU1dVEzQnk3cFVrYmtiZlFWK2x3dlBjaHQyVXlZK0E0MkFKSWlSMjdvT1h1Wk9jNTQKT3liMDNSNWNUR1NuWjJBN0N5VDNubStRak5rV2hXNEpyUE1MWTFJK293dGtRVlF2YW10bnlZNnFEUUtCZ0ZYWgpLT0IzSmxjSzhZa0R5Nm5WeUhkZUhvbGNHaU55YjkxTlN6MUUrWHZIYklnWEdZdmRtUFhoaXRyRGFNQzR1RjBGCjJoRjZQMTlxWnpDOUZqZnY3WGRrSTlrYXF5eENQY0dwUTVBcHhZdDhtUGV1bEJWemFqR1NFMHVsNFVhSWxDNXgKL2VQQnVQVjVvZjJXVFhST0Q5eHhZT0pWd0QvZGprekw1ZFlkMW1UUEFvR0JBTWVwY3diVEphZ3BoZk5zOHY0WAprclNoWXplbVpxY2EwQzRFc2QwNGYwTUxHSlVNS3Zpck4zN0g1OUFjT2IvNWtZcTU5WFRwRmJPWjdmYlpHdDZnCkxnM2hWSHRacElOVGJ5Ni9GOTBUZ09Za3RxUnhNVmc3UFBxbjFqdEFiVU15eVpVZFdHcFNNMmI0bXQ5dGhlUDEKblhMR09NWUtnS2JYbjZXWWN5K2U5eW9ICi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0KCg==\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.adyen.san\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -11426,12 +11426,12 @@ " );", "}", "", - "// Response body should have value \"invalid_request\" for \"error type\"", - "if (jsonData?.error?.message) {", + "// Response body should have value \"connector error\" for \"error type\"", + "if (jsonData?.error?.type) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.message' matches 'Card Expired'\",", + " \"[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'\",", " function () {", - " pm.expect(jsonData.error.message).to.eql(\"Card Expired\");", + " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", " },", " );", "}", @@ -13610,10 +13610,10 @@ "// Response body should have value \"invalid_request\" for \"error type\"", "if (jsonData?.error?.message) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.message' matches 'Refund amount exceeds the payment amount'\",", + " \"[POST]::/payments - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'\",", " function () {", " pm.expect(jsonData.error.message).to.eql(", - " \"Refund amount exceeds the payment amount\",", + " \"The refund amount exceeds the amount captured\",", " );", " },", " );", diff --git a/postman/collection-json/bankofamerica.postman_collection.json b/postman/collection-json/bankofamerica.postman_collection.json index 01524d91953..752b77dcd15 100644 --- a/postman/collection-json/bankofamerica.postman_collection.json +++ b/postman/collection-json/bankofamerica.postman_collection.json @@ -2391,9 +2391,9 @@ "// Response body should have value \"processing\" for \"status\" because payment gets succeeded after one day.", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2431,7 +2431,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -2518,9 +2518,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2682,7 +2682,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -2792,9 +2792,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2957,9 +2957,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3221,9 +3221,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3377,9 +3377,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3532,7 +3532,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"3566111111111113\",\"card_exp_month\":\"12\",\"card_exp_year\":\"30\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"3566111111111113\",\"card_exp_month\":\"12\",\"card_exp_year\":\"30\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3619,12 +3619,12 @@ " );", "}", "", - "// Response body should have value \"processing\" for \"status\"", + "// Response body should have value \"partially_captured\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'processing'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", " },", " );", "}", @@ -3766,12 +3766,12 @@ " );", "}", "", - "// Response body should have value \"processing\" for \"status\"", + "// Response body should have value \"partially_captured\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", " },", " );", "}", diff --git a/postman/collection-json/bluesnap.postman_collection.json b/postman/collection-json/bluesnap.postman_collection.json index 34ad07ae67a..fa6c9258b8d 100644 --- a/postman/collection-json/bluesnap.postman_collection.json +++ b/postman/collection-json/bluesnap.postman_collection.json @@ -449,7 +449,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"bluesnap\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\"},\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"test_mode\":false,\"disabled\":false,\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"bluesnap\",\"connector_account_details\":{\"auth_type\":\"BodyKey\",\"api_key\":\"{{connector_api_key}}\",\"key1\":\"{{connector_key1}}\"},\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"test_mode\":false,\"disabled\":false,\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -4199,6 +4199,916 @@ } ] }, + { + "name": "Scenario28-Create partially captured payment with refund", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_capture\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Capture", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"partially_captured\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6000);", + " },", + " );", + "}", + "", + "// Response body should have value \"0\" for \"amount_received\"", + "if (jsonData?.amount_capturable) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_capturable' matches '0'\",", + " function () {", + " pm.expect(jsonData.amount_capturable).to.eql(0);", + " },", + " );", + "}", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/capture", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "capture" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To capture the funds for an uncaptured payment" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"partially_captured\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + }, + { + "name": "Refunds - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '4000'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(4000);", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":4000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + }, + { + "name": "Refunds - Create-copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(2000);", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":2000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + }, + { + "name": "Refunds - Retrieve Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"2000\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '2000'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(2000);", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/refunds/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" + } + ] + }, + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + }, + { + "name": "Refunds - Validation should throw", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/refunds - Status code is 4xx\", function () {", + " pm.response.to.be.error;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.error.message) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'message' matches 'The refund amount exceeds the amount captured'\",", + " function () {", + " pm.expect(jsonData.error.message).to.eql(\"The refund amount exceeds the amount captured\");", + " },", + " );", + "}", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":2000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + }, + { + "name": "Payments - Retrieve Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"partially_captured\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " },", + " );", + "}", + "", + "// Check if the \"refunds\" array exists", + "pm.test(\"Check if 'refunds' array exists\", function() {", + " pm.expect(jsonData.refunds).to.be.an(\"array\");", + "});", + "", + "// Check if there are exactly 2 items in the \"refunds\" array", + "pm.test(\"Check if there are 2 refunds\", function() {", + " pm.expect(jsonData.refunds.length).to.equal(2);", + "});", + "", + "", + "", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, { "name": "Scenario8-Refund full payment", "item": [ diff --git a/postman/collection-json/checkout.postman_collection.json b/postman/collection-json/checkout.postman_collection.json index 54892f116a0..d510b1c2a17 100644 --- a/postman/collection-json/checkout.postman_collection.json +++ b/postman/collection-json/checkout.postman_collection.json @@ -424,7 +424,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"checkout\",\"connector_account_details\":{\"auth_type\":\"SignatureKey\",\"api_key\":\"{{connector_api_key}}\",\"api_secret\":\"{{connector_api_secret}}\",\"key1\":\"{{connector_key1}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"Visa\",\"Mastercard\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":false,\"installment_payment_enabled\":false}]}],\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"checkout\",\"connector_account_details\":{\"auth_type\":\"SignatureKey\",\"api_key\":\"{{connector_api_key}}\",\"api_secret\":\"{{connector_api_secret}}\",\"key1\":\"{{connector_key1}}\"},\"test_mode\":false,\"disabled\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"payment_methods_enabled\":[{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"debit\",\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"],\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"paypal\",\"payment_experience\":\"redirect_to_url\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":false,\"installment_payment_enabled\":false}]}],\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -559,6 +559,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -639,7 +650,7 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], @@ -647,6 +658,12 @@ "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", @@ -944,12 +961,12 @@ " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", "};", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}" @@ -995,6 +1012,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -1418,12 +1446,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -1510,6 +1538,17 @@ { "name": "Payments - Retrieve-copy", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -2239,16 +2278,16 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", - "// Response body should have value \"adyen\" for \"connector\"", + "// Response body should have value \"checkout\" for \"connector\"", "if (jsonData?.connector) {", " pm.test(", " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'checkout'\",", @@ -2330,6 +2369,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -2963,6 +3013,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -3177,12 +3238,12 @@ " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", "};", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}" @@ -3228,6 +3289,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -3908,6 +3980,17 @@ { "name": "Payments - Retrieve-copy", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -4133,12 +4216,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -4198,7 +4281,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4005519200000004\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"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\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"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://google.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4485040371536584\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4216,6 +4299,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -4490,7 +4584,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4005519200000004\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"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\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"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://google.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4005519200000004\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4577,12 +4671,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -4693,6 +4787,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -5069,12 +5174,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -5185,6 +5290,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -5841,6 +5957,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -6318,6 +6445,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -6795,6 +6933,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -7224,6 +7373,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -7414,12 +7574,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -7501,6 +7661,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -7936,12 +8107,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -8023,6 +8194,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -8582,6 +8764,17 @@ { "name": "Payments - Retrieve-copy", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -9337,7 +9530,9 @@ "listen": "prerequest", "script": { "exec": [ - "" + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" ], "type": "text/javascript" } @@ -9810,7 +10005,9 @@ "listen": "prerequest", "script": { "exec": [ - "" + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" ], "type": "text/javascript" } @@ -10055,7 +10252,9 @@ "listen": "prerequest", "script": { "exec": [ - "" + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" ], "type": "text/javascript" } @@ -10299,7 +10498,9 @@ "listen": "prerequest", "script": { "exec": [ - "" + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" ], "type": "text/javascript" } @@ -11167,7 +11368,9 @@ "listen": "prerequest", "script": { "exec": [ - "" + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" ], "type": "text/javascript" } @@ -12351,6 +12554,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -12541,12 +12755,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -12811,12 +13025,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -13059,12 +13273,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"processing\");", " },", " );", "}", @@ -13146,6 +13360,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -13364,13 +13589,13 @@ " );", "}", "", - "// Response body should have value \"Refund amount exceeds the payment amount\" for \"message\"", + "// Response body should have value \"The refund amount exceeds the amount captured\" for \"message\"", "if (jsonData?.error?.message) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'Refund amount exceeds the payment amount'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'\",", " function () {", " pm.expect(jsonData.error.message).to.eql(", - " \"Refund amount exceeds the payment amount\",", + " \"The refund amount exceeds the amount captured\",", " );", " },", " );", @@ -13541,6 +13766,17 @@ { "name": "Payments - Retrieve", "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "setTimeout(function(){", + " console.log(\"Sleeping for 3 seconds before next request.\");", + "}, 3000);" + ], + "type": "text/javascript" + } + }, { "listen": "test", "script": { @@ -13791,11 +14027,12 @@ ] }, "info": { - "_postman_id": "0da8c3be-4466-413e-9e72-81b21533423e", + "_postman_id": "9ab8f157-6b4b-430a-9ca8-34931682f988", "name": "Checkout Collection", "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "25737662" + "_exporter_id": "25180378", + "_collection_link": "https://galactic-desert-365744.postman.co/workspace/postman-tests-for-all-connector~f274d40a-132c-47e3-8240-6793392ee4d1/collection/25180378-9ab8f157-6b4b-430a-9ca8-34931682f988?action=share&creator=25180378&source=collection_link" }, "variable": [ { diff --git a/postman/collection-json/prophetpay.postman_collection.json b/postman/collection-json/prophetpay.postman_collection.json new file mode 100644 index 00000000000..bd946301f55 --- /dev/null +++ b/postman/collection-json/prophetpay.postman_collection.json @@ -0,0 +1,1418 @@ +{ + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"[LOG]::payment_id - \" + jsonData.payment_id);", + "}", + "", + "console.log(\"[LOG]::x-request-id - \" + pm.response.headers.get(\"x-request-id\"));", + "" + ], + "type": "text/javascript" + } + } + ], + "item": [ + { + "name": "Health check", + "item": [ + { + "name": "New Request", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/accounts - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "url": { + "raw": "{{baseUrl}}/health", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "health" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Flow Testcases", + "item": [ + { + "name": "QuickStart", + "item": [ + { + "name": "Merchant Account - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/accounts - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/accounts - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set merchant_id as variable for jsonData.merchant_id", + "if (jsonData?.merchant_id) {", + " pm.collectionVariables.set(\"merchant_id\", jsonData.merchant_id);", + " console.log(", + " \"- use {{merchant_id}} as collection variable for value\",", + " jsonData.merchant_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{merchant_id}}, as jsonData.merchant_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set api_key as variable for jsonData.api_key", + "if (jsonData?.api_key) {", + " pm.collectionVariables.set(\"api_key\", jsonData.api_key);", + " console.log(", + " \"- use {{api_key}} as collection variable for value\",", + " jsonData.api_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set publishable_key as variable for jsonData.publishable_key", + "if (jsonData?.publishable_key) {", + " pm.collectionVariables.set(\"publishable_key\", jsonData.publishable_key);", + " console.log(", + " \"- use {{publishable_key}} as collection variable for value\",", + " jsonData.publishable_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{publishable_key}}, as jsonData.publishable_key is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"merchant_id\":\"postman_merchant_GHAction_{{$guid}}\",\"locker_id\":\"m0010\",\"merchant_name\":\"NewAge Retailer\",\"merchant_details\":{\"primary_contact_person\":\"John Test\",\"primary_email\":\"JohnTest@test.com\",\"primary_phone\":\"sunt laborum\",\"secondary_contact_person\":\"John Test2\",\"secondary_email\":\"JohnTest2@test.com\",\"secondary_phone\":\"cillum do dolor id\",\"website\":\"www.example.com\",\"about_business\":\"Online Retail with a wide selection of organic products for North America\",\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\"}},\"return_url\":\"https://duck.com\",\"webhook_details\":{\"webhook_version\":\"1.0.1\",\"webhook_username\":\"ekart_retail\",\"webhook_password\":\"password_ekart@123\",\"payment_created_enabled\":true,\"payment_succeeded_enabled\":true,\"payment_failed_enabled\":true},\"sub_merchants_enabled\":false,\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"},\"primary_business_details\":[{\"country\":\"US\",\"business\":\"default\"}]}" + }, + "url": { + "raw": "{{baseUrl}}/accounts", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "accounts" + ] + }, + "description": "Create a new account for a merchant. The merchant could be a seller or retailer or client who likes to receive and send payments." + }, + "response": [] + }, + { + "name": "API Key - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/api_keys/:merchant_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/api_keys/:merchant_id - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set api_key_id as variable for jsonData.key_id", + "if (jsonData?.key_id) {", + " pm.collectionVariables.set(\"api_key_id\", jsonData.key_id);", + " console.log(", + " \"- use {{api_key_id}} as collection variable for value\",", + " jsonData.key_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key_id}}, as jsonData.key_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set api_key as variable for jsonData.api_key", + "if (jsonData?.api_key) {", + " pm.collectionVariables.set(\"api_key\", jsonData.api_key);", + " console.log(", + " \"- use {{api_key}} as collection variable for value\",", + " jsonData.api_key,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{api_key}}, as jsonData.api_key is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\"name\":\"API Key 1\",\"description\":null,\"expiration\":\"2069-09-23T01:02:03.000Z\"}" + }, + "url": { + "raw": "{{baseUrl}}/api_keys/:merchant_id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api_keys", + ":merchant_id" + ], + "variable": [ + { + "key": "merchant_id", + "value": "{{merchant_id}}" + } + ] + } + }, + "response": [] + }, + { + "name": "Payment Connector - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(", + " \"[POST]::/account/:account_id/connectors - Status code is 2xx\",", + " function () {", + " pm.response.to.be.success;", + " },", + ");", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/account/:account_id/connectors - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id", + "if (jsonData?.merchant_connector_id) {", + " pm.collectionVariables.set(", + " \"merchant_connector_id\",", + " jsonData.merchant_connector_id,", + " );", + " console.log(", + " \"- use {{merchant_connector_id}} as collection variable for value\",", + " jsonData.merchant_connector_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"prophetpay\",\"connector_account_details\":{\"auth_type\":\"SignatureKey\",\"api_key\":\"{{connector_api_key}}\",\"api_secret\":\"{{connector_api_secret}}\",\"key1\":\"{{connector_key1}}\"},\"business_country\":\"US\",\"business_label\":\"default\",\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"card_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"card_redirect\",\"payment_experience\":\"redirect_to_url\",\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors" + ], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}", + "description": "(Required) The unique identifier for the merchant account" + } + ] + }, + "description": "Create a new Payment Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialised services like Fraud / Accounting etc." + }, + "response": [] + }, + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":10000,\"currency\":\"USD\",\"confirm\":true,\"amount_to_capture\":10000,\"business_country\":\"US\",\"customer_id\":\"not_a_rick_roll\",\"return_url\":\"https://www.google.com\",\"payment_method\":\"card_redirect\",\"payment_method_type\":\"card_redirect\",\"payment_method_data\":{\"card_redirect\":{\"card_redirect\":{}}},\"routing\":{\"type\":\"single\",\"data\":\"prophetpay\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Happy Cases", + "item": [ + { + "name": "Scenario1-Create payment with confirm true", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":8000,\"currency\":\"USD\",\"confirm\":true,\"amount_to_capture\":8000,\"business_country\":\"US\",\"customer_id\":\"not_a_rick_roll\",\"return_url\":\"https://www.google.com\",\"payment_method\":\"card_redirect\",\"payment_method_type\":\"card_redirect\",\"payment_method_data\":{\"card_redirect\":{\"card_redirect\":{}}},\"routing\":{\"type\":\"single\",\"data\":\"prophetpay\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario2-Create payment with confirm false", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":8000,\"currency\":\"USD\",\"confirm\":false,\"amount_to_capture\":8000,\"business_country\":\"US\",\"customer_id\":\"not_a_rick_roll\",\"return_url\":\"https://www.google.com\",\"routing\":{\"type\":\"single\",\"data\":\"prophetpay\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"card_redirect\",\"payment_method_type\":\"card_redirect\",\"payment_method_data\":{\"card_redirect\":{\"card_redirect\":{}}},\"client_secret\":\"{{client_secret}}\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + } + ] + } + ] + } + ], + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "info": { + "_postman_id": "a553df38-fa33-4522-b029-1cd32821730e", + "name": "Prophetpay Postman Collection", + "description": "## Get started\n\nJuspay Router provides a collection of APIs that enable you to process and manage payments. Our APIs accept and return JSON in the HTTP body, and return standard HTTP response codes. \nYou can consume the APIs directly using your favorite HTTP/REST library. \nWe have a testing environment referred to \"sandbox\", which you can setup to test API calls without affecting production data.\n\n### Base URLs\n\nUse the following base URLs when making requests to the APIs:\n\n| Environment | Base URL |\n| --- | --- |\n| Sandbox | [https://sandbox.hyperswitch.io](https://sandbox.hyperswitch.io) |\n| Production | [https://router.juspay.io](https://router.juspay.io) |\n\n# Authentication\n\nWhen you sign up for an account, you are given a secret key (also referred as api-key). You may authenticate all API requests with Juspay server by providing the appropriate key in the request Authorization header. \nNever share your secret api keys. Keep them guarded and secure.\n\nContact Support: \nName: Juspay Support \nEmail: [support@juspay.in](mailto:support@juspay.in)", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "24206034" + }, + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + }, + { + "key": "admin_api_key", + "value": "", + "type": "string" + }, + { + "key": "api_key", + "value": "", + "type": "string" + }, + { + "key": "merchant_id", + "value": "" + }, + { + "key": "payment_id", + "value": "" + }, + { + "key": "customer_id", + "value": "" + }, + { + "key": "mandate_id", + "value": "" + }, + { + "key": "payment_method_id", + "value": "" + }, + { + "key": "refund_id", + "value": "" + }, + { + "key": "merchant_connector_id", + "value": "" + }, + { + "key": "client_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_api_key", + "value": "", + "type": "string" + }, + { + "key": "connector_api_secret", + "value": "", + "type": "string" + }, + { + "key": "connector_key1", + "value": "", + "type": "string" + }, + { + "key": "publishable_key", + "value": "", + "type": "string" + }, + { + "key": "api_key_id", + "value": "", + "type": "string" + }, + { + "key": "payment_token", + "value": "" + }, + { + "key": "gateway_merchant_id", + "value": "", + "type": "string" + }, + { + "key": "certificate", + "value": "", + "type": "string" + }, + { + "key": "certificate_keys", + "value": "", + "type": "string" + } + ] +} diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index 4d3e548f535..e158ccd1a5e 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -423,7 +423,7 @@ } ], "url": { - "raw": "{{baseUrl}}/accounts/list", + "raw": "{{baseUrl}}/accounts/list?organization_id={{organization_id}}", "host": [ "{{baseUrl}}" ], @@ -434,8 +434,7 @@ "query": [ { "key": "organization_id", - "value": "{{organization_id}}", - "disabled": false + "value": "{{organization_id}}" } ], "variable": [ @@ -5665,7 +5664,7 @@ "language": "json" } }, - "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"Visa\",\"Mastercard\"]}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"Visa\",\"Mastercard\"]}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" + "raw": "{\"connector_type\":\"fiz_operations\",\"connector_name\":\"stripe\",\"business_country\":\"US\",\"business_label\":\"default\",\"connector_account_details\":{\"auth_type\":\"HeaderKey\",\"api_key\":\"{{connector_api_key}}\"},\"test_mode\":false,\"disabled\":false,\"payment_methods_enabled\":[{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"affirm\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"afterpay_clearpay\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"redirect_to_url\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"pay_later\",\"payment_method_types\":[{\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"payment_experience\":\"invoke_sdk_client\",\"payment_method_type\":\"klarna\"}]},{\"payment_method\":\"bank_redirect\",\"payment_method_types\":[{\"payment_method_type\":\"ideal\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"giropay\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sofort\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"eps\",\"payment_experience\":null,\"card_networks\":null,\"accepted_currencies\":null,\"accepted_countries\":null,\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_debit\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"becs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"bank_transfer\",\"payment_method_types\":[{\"payment_method_type\":\"ach\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"bacs\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"sepa\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"credit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"card\",\"payment_method_types\":[{\"payment_method_type\":\"debit\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true,\"card_networks\":[\"AmericanExpress\",\"Discover\",\"Interac\",\"JCB\",\"Mastercard\",\"Visa\",\"DinersClub\",\"UnionPay\",\"RuPay\"]}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"apple_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]},{\"payment_method\":\"wallet\",\"payment_method_types\":[{\"payment_method_type\":\"google_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true},{\"payment_method_type\":\"we_chat_pay\",\"payment_experience\":\"invoke_sdk_client\",\"minimum_amount\":1,\"maximum_amount\":68607706,\"recurring_enabled\":true,\"installment_payment_enabled\":true}]}],\"metadata\":{\"google_pay\":{\"allowed_payment_methods\":[{\"type\":\"CARD\",\"parameters\":{\"allowed_auth_methods\":[\"PAN_ONLY\",\"CRYPTOGRAM_3DS\"],\"allowed_card_networks\":[\"AMEX\",\"DISCOVER\",\"INTERAC\",\"JCB\",\"MASTERCARD\",\"VISA\"]},\"tokenization_specification\":{\"type\":\"PAYMENT_GATEWAY\",\"parameters\":{\"gateway\":\"example\",\"gateway_merchant_id\":\"{{gateway_merchant_id}}\"}}}],\"merchant_info\":{\"merchant_name\":\"Narayan Bhat\"}},\"apple_pay\":{\"session_token_data\":{\"initiative\":\"web\",\"certificate\":\"{{certificate}}\",\"display_name\":\"applepay\",\"certificate_keys\":\"{{certificate_keys}}\",\"initiative_context\":\"hyperswitch-sdk-test.netlify.app\",\"merchant_identifier\":\"merchant.com.stripe.sang\"},\"payment_request_data\":{\"label\":\"applepay pvt.ltd\",\"supported_networks\":[\"visa\",\"masterCard\",\"amex\",\"discover\"],\"merchant_capabilities\":[\"supports3DS\"]}}}}" }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", @@ -6055,7 +6054,7 @@ "name": "Happy Cases", "item": [ { - "name": "Scenario24-Add card flow", + "name": "Scenario1-Create payment with confirm true", "item": [ { "name": "Payments - Create", @@ -6064,62 +6063,87 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", - "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", - "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", - " pm.test(\"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\", function() {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " })};" + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" ], "type": "text/javascript" } @@ -6144,7 +6168,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"stripesavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4111111111111111\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -6160,32 +6184,93 @@ "response": [] }, { - "name": "List payment methods for a Customer", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "if (jsonData?.customer_payment_methods[0]?.payment_token) {", - " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", - " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", - "}" + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"Succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" ], "type": "text/javascript" } @@ -6200,126 +6285,125 @@ } ], "url": { - "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "customers", - ":customer_id", - "payment_methods" + "payments", + ":id" ], "query": [ { - "key": "accepted_country", - "value": "co", - "disabled": true - }, - { - "key": "accepted_country", - "value": "pa", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "voluptate ea", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "exercitation", - "disabled": true - }, - { - "key": "minimum_amount", - "value": "100", - "disabled": true - }, - { - "key": "maximum_amount", - "value": "10000000", - "disabled": true - }, - { - "key": "recurring_payment_enabled", - "value": "true", - "disabled": true - }, - { - "key": "installment_payment_enabled", - "value": "true", - "disabled": true + "key": "force_sync", + "value": "true" } ], "variable": [ { - "key": "customer_id", - "value": "{{customer_id}}", - "description": "//Pass the customer id" + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To filter and list the applicable payment methods for a particular Customer ID" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario2-Create payment with confirm false", + "item": [ { - "name": "Save card payments - Create", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};" + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" ], "type": "text/javascript" } @@ -6344,7 +6428,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"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\",\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -6360,7 +6444,7 @@ "response": [] }, { - "name": "Save card payments - Confirm", + "name": "Payments - Confirm", "event": [ { "listen": "test", @@ -6440,17 +6524,15 @@ " },", " );", "}", - "", - "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", - " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - " },", - " );", - "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ "" ], "type": "text/javascript" @@ -6496,7 +6578,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\",\"card_cvc\":\"737\"}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"Joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -6539,17 +6621,17 @@ " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", @@ -6588,6 +6670,16 @@ " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", " );", "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", "" ], "type": "text/javascript" @@ -6603,7 +6695,7 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], @@ -6611,6 +6703,12 @@ "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", @@ -6622,48 +6720,103 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario2a-Create payment with confirm false card holder name null", + "item": [ { - "name": "Refunds - Create Copy", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", " );", "}", "" ], "type": "text/javascript" } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } } ], "request": { @@ -6685,38 +6838,46 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":600,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"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\",\"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\"}}" }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments" ] }, - "description": "To create a refund against an already processed payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Refunds - Retrieve", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", @@ -6725,121 +6886,90 @@ " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/refunds/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "refunds", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" - } - ] - }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" - }, - "response": [] - } - ] - }, - { - "name": "Scenario25-Don't Pass CVV for save card flow and verifysuccess payment", - "item": [ - { - "name": "Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};" + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" ], "type": "text/javascript" } } ], "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, "method": "POST", "header": [ { @@ -6858,48 +6988,109 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"stripesavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"371449635398431\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"7373\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":null,\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] }, { - "name": "List payment methods for a Customer", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "if (jsonData?.customer_payment_methods[0]?.payment_token) {", - " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", - " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", - "}" + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" ], "type": "text/javascript" } @@ -6914,126 +7105,125 @@ } ], "url": { - "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "customers", - ":customer_id", - "payment_methods" + "payments", + ":id" ], "query": [ { - "key": "accepted_country", - "value": "co", - "disabled": true - }, - { - "key": "accepted_country", - "value": "pa", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "voluptate ea", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "exercitation", - "disabled": true - }, - { - "key": "minimum_amount", - "value": "100", - "disabled": true - }, - { - "key": "maximum_amount", - "value": "10000000", - "disabled": true - }, - { - "key": "recurring_payment_enabled", - "value": "true", - "disabled": true - }, + "key": "force_sync", + "value": "true" + } + ], + "variable": [ { - "key": "installment_payment_enabled", - "value": "true", - "disabled": true - } - ], - "variable": [ - { - "key": "customer_id", - "value": "{{customer_id}}", - "description": "//Pass the customer id" + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To filter and list the applicable payment methods for a particular Customer ID" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario2b-Create payment with confirm false card holder name empty", + "item": [ { - "name": "Save card payments - Create", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};" + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" ], "type": "text/javascript" } @@ -7058,7 +7248,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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\":6540,\"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\",\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -7074,14 +7264,14 @@ "response": [] }, { - "name": "Save card payments - Confirm", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", @@ -7145,21 +7335,24 @@ " );", "}", "", - "// Response body should have value \"adyen\" for \"connector\"", - "if (jsonData?.connector) {", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(\"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\", function() {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " })};" + "}" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" ], "type": "text/javascript" } @@ -7204,7 +7397,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\"}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -7227,117 +7420,6 @@ "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] - } - ] - }, - { - "name": "Scenario26-Save card payment with manual capture", - "item": [ - { - "name": "Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", - "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", - "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"adyensavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"371449635398431\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"7373\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] }, { "name": "Payments - Retrieve", @@ -7408,51 +7490,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", - "}", - "", - "// Validate the connector", - "pm.test(\"[POST]::/payments - connector\", function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - "});", - "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", - " );", - "}", - "", - "// Response body should have value \"6000\" for \"amount_received\"", - "if (jsonData?.amount_received) {", - " pm.test(", - " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", - " function () {", - " pm.expect(jsonData.amount_received).to.eql(6540);", - " },", - " );", - "}", - "", - "// Response body should have value \"6540\" for \"amount_capturable\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", - " function () {", - " pm.expect(jsonData.amount_capturable).to.eql(0);", - " },", - " );", - "}", - "" + "}" ], "type": "text/javascript" } @@ -7492,168 +7538,91 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, - { - "name": "List payment methods for a Customer", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx ", - "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", - "", - "if (jsonData?.customer_payment_methods[0]?.payment_token) {", - " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", - " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", - "} else {", - " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", - "}" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "customers", - ":customer_id", - "payment_methods" - ], - "query": [ - { - "key": "accepted_country", - "value": "co", - "disabled": true - }, - { - "key": "accepted_country", - "value": "pa", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "voluptate ea", - "disabled": true - }, - { - "key": "accepted_currency", - "value": "exercitation", - "disabled": true - }, - { - "key": "minimum_amount", - "value": "100", - "disabled": true - }, - { - "key": "maximum_amount", - "value": "10000000", - "disabled": true - }, - { - "key": "recurring_payment_enabled", - "value": "true", - "disabled": true - }, - { - "key": "installment_payment_enabled", - "value": "true", - "disabled": true - } - ], - "variable": [ - { - "key": "customer_id", - "value": "{{customer_id}}", - "description": "//Pass the customer id" - } - ] - }, - "description": "To filter and list the applicable payment methods for a particular Customer ID" - }, - "response": [] - }, + } + ] + }, + { + "name": "Scenario3-Create payment without PMD", + "item": [ { - "name": "Save card payments - Create", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};" + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" ], "type": "text/javascript" } @@ -7678,7 +7647,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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\":6540,\"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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -7694,7 +7663,7 @@ "response": [] }, { - "name": "Save card payments - Confirm", + "name": "Payments - Confirm", "event": [ { "listen": "test", @@ -7764,24 +7733,12 @@ " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", " );", "}", - "", - "// Response body should have value \"requires_capture\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_capture'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_capture\");", - " },", - " );", - "}", - "", - "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -7789,6 +7746,15 @@ ], "type": "text/javascript" } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } } ], "request": { @@ -7830,7 +7796,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\",\"card_cvc\":\"7373\"}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -7855,29 +7821,26 @@ "response": [] }, { - "name": "Payments - Capture", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -7929,44 +7892,133 @@ "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario4-Create payment with Manual capture", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", "", - "// Validate the connector", - "pm.test(\"[POST]::/payments - connector\", function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"6000\" for \"amount_received\"", - "if (jsonData?.amount_received) {", - " pm.test(", - " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount_received).to.eql(6540);", - " },", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount_capturable\"", - "if (jsonData?.amount_capturable) {", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_capture\" for \"status\"", + "if (jsonData?.status) {", " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", " function () {", - " pm.expect(jsonData.amount_capturable).to.eql(6540);", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", " },", " );", "}", @@ -7995,51 +8047,45 @@ "language": "json" } }, - "raw": "{\"amount_to_capture\":6540,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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/:id/capture", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "capture" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "To capture the funds for an uncaptured payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Payments - Retrieve-copy", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -8088,21 +8134,16 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", "", - "// Validate the connector", - "pm.test(\"[POST]::/payments - connector\", function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - "});", - "", "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.amount) {", " pm.test(", @@ -8122,16 +8163,6 @@ " },", " );", "}", - "", - "// Response body should have value \"6540\" for \"amount_capturable\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", - " function () {", - " pm.expect(jsonData.amount_capturable).to.eql(0);", - " },", - " );", - "}", "" ], "type": "text/javascript" @@ -8139,27 +8170,35 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount_to_capture\":6540,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "capture" ], "variable": [ { @@ -8169,169 +8208,88 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To capture the funds for an uncaptured payment" }, "response": [] }, { - "name": "Refunds - Create Copy", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", " );", - "}", - "", - "// Response body should have value \"540\" for \"amount\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(540);", - " },", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Validate the connector", - "pm.test(\"[POST]::/payments - connector\", function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - "});", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" - }, - "url": { - "raw": "{{baseUrl}}/refunds", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "refunds" - ] - }, - "description": "To create a refund against an already processed payment" - }, - "response": [] - }, - { - "name": "Refunds - Retrieve Copy", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", " );", "}", "", "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", - "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(540);", - " },", - " );", - "}", "" ], "type": "text/javascript" @@ -8347,30 +8305,36 @@ } ], "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", + "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] } ] }, { - "name": "Scenario27-Create a failure card payment with confirm true", + "name": "Scenario4a-Create payment with manual_multiple capture", "item": [ { "name": "Payments - Create", @@ -8441,41 +8405,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_capture\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", - " },", - " );", - "}", - "", - "// Response body should have \"connector_transaction_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", - " .true;", - " },", - ");", - "", - "// Response body should have value \"card_declined\" for \"error_code\"", - "if (jsonData?.error_code) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", - " function () {", - " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", - " },", - " );", - "}", - "", - "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", - "if (jsonData?.error_message) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", " function () {", - " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", " },", " );", "}", @@ -8504,7 +8439,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000009995\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -8520,26 +8455,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -8588,41 +8526,32 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'failed'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", " },", " );", "}", "", - "// Response body should have \"connector_transaction_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", - " .true;", - " },", - ");", - "", - "// Response body should have value \"card_declined\" for \"error_code\"", - "if (jsonData?.error_code) {", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", " function () {", - " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", + " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", "", - "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", - "if (jsonData?.error_message) {", + "// Response body should have value \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", " function () {", - " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", + " pm.expect(jsonData.amount_received).to.eql(6000);", " },", " );", "}", @@ -8633,27 +8562,35 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "capture" ], "variable": [ { @@ -8663,151 +8600,19 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To capture the funds for an uncaptured payment" }, "response": [] - } - ] - }, - { - "name": "Scenario27-Create payment without customer_id and with billing address and shipping address", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"connector_transaction_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", - " .true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Retrieve", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", @@ -8868,24 +8673,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", " },", " );", "}", - "", - "// Response body should have \"connector_transaction_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", - " .true;", - " },", - ");", "" ], "type": "text/javascript" @@ -8930,7 +8726,7 @@ ] }, { - "name": "Scenario4-Create payment with manual_multiple capture", + "name": "Scenario5-Void the payment", "item": [ { "name": "Payments - Create", @@ -9051,20 +8847,20 @@ "response": [] }, { - "name": "Payments - Capture", + "name": "Payments - Cancel", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/cancel - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(", - " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " \"[POST]::/payments/:id/cancel - Content-Type is application/json\",", " function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", @@ -9073,7 +8869,7 @@ ");", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/cancel - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -9096,19 +8892,6 @@ " );", "}", "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", @@ -9122,32 +8905,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"cancelled\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"partially_captured\");", - " },", - " );", - "}", - "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", - " );", - "}", - "", - "// Response body should have value \"6000\" for \"amount_received\"", - "if (jsonData?.amount_received) {", - " pm.test(", - " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " \"[POST]::/payments/:id/cancel - Content check if value for 'status' matches 'cancelled'\",", " function () {", - " pm.expect(jsonData.amount_received).to.eql(6000);", + " pm.expect(jsonData.status).to.eql(\"cancelled\");", " },", " );", "}", @@ -9176,17 +8939,17 @@ "language": "json" } }, - "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + "raw": "{\"cancellation_reason\":\"requested_by_customer\"}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/capture", + "raw": "{{baseUrl}}/payments/:id/cancel", "host": [ "{{baseUrl}}" ], "path": [ "payments", ":id", - "capture" + "cancel" ], "variable": [ { @@ -9196,7 +8959,7 @@ } ] }, - "description": "To capture the funds for an uncaptured payment" + "description": "A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action" }, "response": [] }, @@ -9269,12 +9032,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"cancelled\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'cancelled'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " pm.expect(jsonData.status).to.eql(\"cancelled\");", " },", " );", "}", @@ -9322,7 +9085,7 @@ ] }, { - "name": "Scenario1-Create payment with confirm true", + "name": "Scenario6-Create 3DS payment", "item": [ { "name": "Payments - Create", @@ -9393,21 +9156,21 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", "", - "// Response body should have \"connector_transaction_id\"", + "// Response body should have \"next_action.redirect_to_url\"", "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", " .true;", " },", ");", @@ -9436,7 +9199,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"business_country\":\"US\",\"business_label\":\"default\",\"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\":\"three_ds\",\"return_url\":\"https://duck.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -9520,24 +9283,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"connector_transaction_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", - " .true;", - " },", - ");", "" ], "type": "text/javascript" @@ -9582,7 +9336,7 @@ ] }, { - "name": "Scenario2-Create payment with confirm false", + "name": "Scenario7-Create 3DS payment with confrm false", "item": [ { "name": "Payments - Create", @@ -9687,7 +9441,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6540,\"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\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -9774,41 +9528,21 @@ " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", - " );", - "}", - "", - "// Response body should have value \"6540\" for \"amount_capturable\"", - "if (jsonData?.amount) {", + "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 0'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount_capturable).to.eql(0);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"connector_transaction_id\"", + "// Response body should have \"next_action.redirect_to_url\"", "pm.test(", - " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " \"[POST]::/payments/:id/confirm - Content check if 'next_action.redirect_to_url' exists\",", " function () {", - " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", " .true;", " },", ");", @@ -9959,32 +9693,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", - " );", - "}", - "", - "// Response body should have value \"6540\" for \"amount_capturable\"", - "if (jsonData?.amount) {", - " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 0'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount_capturable).to.eql(0);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", @@ -10032,7 +9746,7 @@ ] }, { - "name": "Scenario3-Create payment without PMD", + "name": "Scenario8-Create a failure card payment with confirm true", "item": [ { "name": "Payments - Create", @@ -10103,12 +9817,41 @@ " );", "}", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " pm.expect(jsonData.status).to.eql(\"failed\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"card_declined\" for \"error_code\"", + "if (jsonData?.error_code) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", + " function () {", + " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", + " },", + " );", + "}", + "", + "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", + "if (jsonData?.error_message) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " function () {", + " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", " },", " );", "}", @@ -10137,7 +9880,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000009995\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -10153,29 +9896,26 @@ "response": [] }, { - "name": "Payments - Confirm", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -10223,24 +9963,45 @@ " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", " );", "}", - "// Response body should have value \"succeeded\" for \"status\"", + "", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'failed'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"failed\");", + " },", + " );", + "}", + "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"card_declined\" for \"error_code\"", + "if (jsonData?.error_code) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_code' matches 'card_declined'\",", + " function () {", + " pm.expect(jsonData.error_code).to.eql(\"card_declined\");", + " },", + " );", + "}", + "", + "// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"", + "if (jsonData?.error_message) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",", + " function () {", + " pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");", " },", " );", "}", - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ "" ], "type": "text/javascript" @@ -10248,55 +10009,27 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\"}" - }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id", - "confirm" + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } ], "variable": [ { @@ -10306,31 +10039,36 @@ } ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario9-Refund full payment", + "item": [ { - "name": "Payments - Retrieve", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -10382,7 +10120,7 @@ "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", @@ -10395,66 +10133,60 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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/:id?force_sync=true", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] - } - ] - }, - { - "name": "Scenario4-Create payment with Manual capture", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -10503,12 +10235,12 @@ " );", "}", "", - "// Response body should have value \"requires_capture\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -10519,64 +10251,57 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Capture", + "name": "Refunds - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", "// Set response object as internal variable", @@ -10585,49 +10310,23 @@ " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", @@ -10635,24 +10334,14 @@ "}", "", "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.amount) {", + "if (jsonData?.status) {", " pm.test(", - " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", " function () {", " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", - "", - "// Response body should have value \"6000\" for \"amount_received\"", - "if (jsonData?.amount_received) {", - " pm.test(", - " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount_received).to.eql(6540);", - " },", - " );", - "}", "" ], "type": "text/javascript" @@ -10678,105 +10367,75 @@ "language": "json" } }, - "raw": "{\"amount_to_capture\":6540,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/capture", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "capture" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "refunds" ] }, - "description": "To capture the funds for an uncaptured payment" + "description": "To create a refund against an already processed payment" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Refunds - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", @@ -10795,36 +10454,30 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "payments", + "refunds", ":id" ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], "variable": [ { "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "value": "{{refund_id}}", + "description": "(Required) unique refund id" } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] } ] }, { - "name": "Scenario5-Void the payment", + "name": "Scenario9a-Partial refund", "item": [ { "name": "Payments - Create", @@ -10895,12 +10548,12 @@ " );", "}", "", - "// Response body should have value \"requires_capture\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -10929,7 +10582,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -10945,29 +10598,26 @@ "response": [] }, { - "name": "Payments - Cancel", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/cancel - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/cancel - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/cancel - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -10990,6 +10640,19 @@ " );", "}", "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", @@ -11003,12 +10666,12 @@ " );", "}", "", - "// Response body should have value \"cancelled\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/cancel - Content check if value for 'status' matches 'cancelled'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"cancelled\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -11019,35 +10682,27 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"cancellation_reason\":\"requested_by_customer\"}" - }, "url": { - "raw": "{{baseUrl}}/payments/:id/cancel", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id", - "cancel" + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } ], "variable": [ { @@ -11057,85 +10712,64 @@ } ] }, - "description": "A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Refunds - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"cancelled\" for \"status\"", + "// Response body should have value \"540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'cancelled'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"cancelled\");", + " pm.expect(jsonData.amount).to.eql(540);", " },", " );", "}", @@ -11146,132 +10780,96 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "refunds" ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To create a refund against an already processed payment" }, "response": [] - } - ] - }, - { - "name": "Scenario6-Create 3DS payment", - "item": [ + }, { - "name": "Payments - Create", + "name": "Refunds - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.amount).to.eql(540);", " },", " );", "}", - "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", - " },", - ");", "" ], "type": "text/javascript" @@ -11279,114 +10877,185 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"business_country\":\"US\",\"business_label\":\"default\",\"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\":\"three_ds\",\"return_url\":\"https://duck.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "refunds", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Refunds - Create-copy", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", " );", "}", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + "// Response body should have value \"1000\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(1000);", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":1000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + }, + { + "name": "Refunds - Retrieve-copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(1000);", " },", " );", "}", @@ -11405,58 +11074,47 @@ } ], "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "payments", + "refunds", ":id" ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], "variable": [ { "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "value": "{{refund_id}}", + "description": "(Required) unique refund id" } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - } - ] - }, - { - "name": "Scenario7-Create 3DS payment with confrm false", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Retrieve-copy", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -11505,15 +11163,20 @@ " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "", + "// Response body should have \"refunds\"", + "pm.test(\"[POST]::/payments - Content check if 'refunds' exists\", function () {", + " pm.expect(typeof jsonData.refunds !== \"undefined\").to.be.true;", + "});", "" ], "type": "text/javascript" @@ -11521,63 +11184,66 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":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\":\"three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000003063\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario10-Create a mandate and recurring payment", + "item": [ { - "name": "Payments - Confirm", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -11626,33 +11292,41 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", "", - "// Response body should have \"next_action.redirect_to_url\"", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have \"mandate_id\"", "pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if 'next_action.redirect_to_url' exists\",", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", " },", ");", - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ "" ], "type": "text/javascript" @@ -11660,26 +11334,6 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -11698,27 +11352,18 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"single_use\":{\"amount\":7000,\"currency\":\"USD\"}}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, @@ -11791,15 +11436,31 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -11840,14 +11501,9 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - } - ] - }, - { - "name": "Scenario9-Refund full payment", - "item": [ + }, { - "name": "Payments - Create", + "name": "Recurring Payments - Create", "event": [ { "listen": "test", @@ -11924,6 +11580,30 @@ " },", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"payment_method_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'payment_method_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.payment_method_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -11949,7 +11629,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"mandate_id\":\"{{mandate_id}}\",\"off_session\":true,\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -11965,7 +11645,7 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Retrieve-copy", "event": [ { "listen": "test", @@ -12042,6 +11722,22 @@ " },", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -12082,64 +11778,116 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario11-Refund recurring payment", + "item": [ { - "name": "Refunds - Create", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", " );", "}", "", "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -12165,78 +11913,115 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"single_use\":{\"amount\":7000,\"currency\":\"USD\"}}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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}}/refunds", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments" ] }, - "description": "To create a refund against an already processed payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Refunds - Retrieve", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", - " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", - " },", - " );", - "}", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -12252,33 +12037,34 @@ } ], "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", + "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - } - ] - }, - { - "name": "Scenario10-Partial refund", - "item": [ + }, { - "name": "Payments - Create", + "name": "Recurring Payments - Create", "event": [ { "listen": "test", @@ -12355,6 +12141,40 @@ " },", " );", "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"payment_method_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'payment_method_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.payment_method_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -12380,7 +12200,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6570,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6570,\"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\",\"mandate_id\":\"{{mandate_id}}\",\"off_session\":true,\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -12396,7 +12216,7 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Retrieve-copy", "event": [ { "listen": "test", @@ -12473,6 +12293,22 @@ " },", " );", "}", + "", + "// Response body should have \"mandate_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have \"mandate_data\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'mandate_data' exists\",", + " function () {", + " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", + " },", + ");", "" ], "type": "text/javascript" @@ -12515,7 +12351,7 @@ "response": [] }, { - "name": "Refunds - Create", + "name": "Refunds - Create Copy", "event": [ { "listen": "test", @@ -12562,12 +12398,12 @@ " );", "}", "", - "// Response body should have value \"540\" for \"amount\"", + "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(540);", + " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", @@ -12596,7 +12432,7 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/refunds", @@ -12612,7 +12448,7 @@ "response": [] }, { - "name": "Refunds - Retrieve", + "name": "Refunds - Retrieve Copy", "event": [ { "listen": "test", @@ -12662,9 +12498,9 @@ "// Response body should have value \"6540\" for \"amount\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", + " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(540);", + " pm.expect(jsonData.amount).to.eql(6540);", " },", " );", "}", @@ -12702,61 +12538,87 @@ "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario12-BNPL-klarna", + "item": [ { - "name": "Refunds - Create-copy", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Response body should have value \"1000\" for \"amount\"", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(1000);", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", @@ -12785,38 +12647,46 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":1000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "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}}/refunds", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments" ] }, - "description": "To create a refund against an already processed payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Refunds - Retrieve-copy", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", @@ -12825,75 +12695,163 @@ " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '1000'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(1000);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "" + "", + "// Response body should have \"next_action.redirect_to_url\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", + " function () {", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"klarna\" for \"payment_method_type\"", + "if (jsonData?.payment_method_type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'klarna'\",", + " function () {", + " pm.expect(jsonData.payment_method_type).to.eql(\"klarna\");", + " },", + " );", + "}", + "", + "// Response body should have value \"stripe\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"klarna_redirect\":{\"issuer_name\":\"stripe\",\"billing_email\":\"arjun.karthik@juspay.in\",\"billing_country\":\"US\"}}},\"client_secret\":\"{{client_secret}}\"}" + }, "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", - ":id" + "payments", + ":id", + "confirm" ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] }, { - "name": "Payments - Retrieve-copy", + "name": "Payments - Retrieve", "event": [ { "listen": "test", @@ -12961,20 +12919,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"refunds\"", - "pm.test(\"[POST]::/payments - Content check if 'refunds' exists\", function () {", - " pm.expect(typeof jsonData.refunds !== \"undefined\").to.be.true;", - "});", "" ], "type": "text/javascript" @@ -13019,7 +12972,7 @@ ] }, { - "name": "Scenario11-Create a mandate and recurring payment", + "name": "Scenario13-BNPL-afterpay", "item": [ { "name": "Payments - Create", @@ -13090,41 +13043,15 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", "" ], "type": "text/javascript" @@ -13150,7 +13077,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"single_use\":{\"amount\":7000,\"currency\":\"USD\"}}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"SE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -13166,26 +13093,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -13234,31 +13164,53 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", "", - "// Response body should have \"mandate_id\"", + "// Response body should have \"next_action.redirect_to_url\"", "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", " },", ");", "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", + "// Response body should have value \"afterpay_clearpay\" for \"payment_method_type\"", + "if (jsonData?.payment_method_type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'afterpay_clearpay'\",", + " function () {", + " pm.expect(jsonData.payment_method_type).to.eql(\"afterpay_clearpay\");", + " },", + " );", + "}", + "", + "// Response body should have value \"stripe\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ "" ], "type": "text/javascript" @@ -13266,27 +13218,55 @@ } ], "request": { - "method": "GET", + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"afterpay_clearpay_redirect\":{\"billing_name\":\"Akshaya\",\"billing_email\":\"example@example.com\"}}},\"client_secret\":\"{{client_secret}}\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "confirm" ], "variable": [ { @@ -13296,166 +13276,24 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] }, { - "name": "Recurring Payments - Create", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"payment_method_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'payment_method_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.payment_method_data !== \"undefined\").to.be.true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"mandate_id\":\"{{mandate_id}}\",\"off_session\":true,\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Retrieve-copy", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", @@ -13511,31 +13349,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", "" ], "type": "text/javascript" @@ -13580,7 +13402,7 @@ ] }, { - "name": "Scenario11-Refund recurring payment", + "name": "Scenario14-BNPL-affirm", "item": [ { "name": "Payments - Create", @@ -13651,41 +13473,15 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", "" ], "type": "text/javascript" @@ -13711,7 +13507,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"single_use\":{\"amount\":7000,\"currency\":\"USD\"}}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"US\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -13727,26 +13523,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -13795,59 +13594,109 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", "", - "// Response body should have \"mandate_id\"", + "// Response body should have \"next_action.redirect_to_url\"", "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", " },", ");", "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ + "// Response body should have value \"affirm\" for \"payment_method_type\"", + "if (jsonData?.payment_method_type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'affirm'\",", + " function () {", + " pm.expect(jsonData.payment_method_type).to.eql(\"affirm\");", + " },", + " );", + "}", + "", + "// Response body should have value \"stripe\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"affirm_redirect\":{\"issuer_name\":\"affirm\",\"billing_email\":\"user-us@example.com\",\"billing_country\":\"US\"}}},\"client_secret\":\"{{client_secret}}\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "confirm" ], "variable": [ { @@ -13857,31 +13706,31 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] }, { - "name": "Recurring Payments - Create", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -13930,49 +13779,15 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"payment_method_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'payment_method_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.payment_method_data !== \"undefined\").to.be.true;", - " },", - ");", "" ], "type": "text/javascript" @@ -13980,60 +13795,66 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6570,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6570,\"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\",\"mandate_id\":\"{{mandate_id}}\",\"off_session\":true,\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario15-Bank Redirect-Ideal", + "item": [ { - "name": "Payments - Retrieve-copy", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -14082,31 +13903,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", "" ], "type": "text/javascript" @@ -14114,94 +13919,146 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Refunds - Create Copy", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", - "if (jsonData?.status) {", + "// Response body should have \"next_action.redirect_to_url\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", + " function () {", + " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", + " .true;", + " },", + ");", + "", + "// Response body should have value \"ideal\" for \"payment_method_type\"", + "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'ideal'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", + " pm.expect(jsonData.payment_method_type).to.eql(\"ideal\");", + " },", + " );", + "}", + "", + "// Response body should have value \"stripe\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", " },", " );", "}", @@ -14209,9 +14066,38 @@ ], "type": "text/javascript" } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } } ], "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, "method": "POST", "header": [ { @@ -14230,75 +14116,105 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To create a refund against an already processed payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] }, { - "name": "Refunds - Retrieve Copy", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", "// Set response object as internal variable", "let jsonData = {};", "try {", " jsonData = pm.response.json();", "} catch (e) {}", "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", " );", "} else {", " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", " );", "}", "", - "// Response body should have value \"6540\" for \"amount\"", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/refunds - Content check if value for 'amount' matches '6540'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", - " pm.expect(jsonData.amount).to.eql(6540);", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", @@ -14317,30 +14233,36 @@ } ], "url": { - "raw": "{{baseUrl}}/refunds/:id", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "refunds", + "payments", ":id" ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], "variable": [ { "key": "id", - "value": "{{refund_id}}", - "description": "(Required) unique refund id" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] } ] }, { - "name": "Scenario12-BNPL-klarna", + "name": "Scenario16-Bank Redirect-sofort", "item": [ { "name": "Payments - Create", @@ -14445,7 +14367,7 @@ "language": "json" } }, - "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\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -14551,12 +14473,12 @@ " },", ");", "", - "// Response body should have value \"klarna\" for \"payment_method_type\"", + "// Response body should have value \"sofort\" for \"payment_method_type\"", "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'klarna'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'sofort'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"klarna\");", + " pm.expect(jsonData.payment_method_type).to.eql(\"sofort\");", " },", " );", "}", @@ -14624,7 +14546,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"klarna\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"klarna_redirect\":{\"issuer_name\":\"stripe\",\"billing_email\":\"arjun.karthik@juspay.in\",\"billing_country\":\"US\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"sofort\",\"payment_method_data\":{\"bank_redirect\":{\"sofort\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_noe_lb_fur_niederosterreich_u_wien\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -14770,7 +14692,7 @@ ] }, { - "name": "Scenario13-BNPL-afterpay", + "name": "Scenario17-Bank Redirect-eps", "item": [ { "name": "Payments - Create", @@ -14875,7 +14797,7 @@ "language": "json" } }, - "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"SE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -14981,12 +14903,12 @@ " },", ");", "", - "// Response body should have value \"afterpay_clearpay\" for \"payment_method_type\"", + "// Response body should have value \"eps\" for \"payment_method_type\"", "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'afterpay_clearpay'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'eps'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"afterpay_clearpay\");", + " pm.expect(jsonData.payment_method_type).to.eql(\"eps\");", " },", " );", "}", @@ -15054,7 +14976,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"afterpay_clearpay\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"afterpay_clearpay_redirect\":{\"billing_name\":\"Akshaya\",\"billing_email\":\"example@example.com\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -15200,7 +15122,7 @@ ] }, { - "name": "Scenario14-BNPL-affirm", + "name": "Scenario18-Bank Redirect-giropay", "item": [ { "name": "Payments - Create", @@ -15305,7 +15227,7 @@ "language": "json" } }, - "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"US\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -15411,12 +15333,12 @@ " },", ");", "", - "// Response body should have value \"affirm\" for \"payment_method_type\"", + "// Response body should have value \"giropay\" for \"payment_method_type\"", "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'affirm'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'giropay'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"affirm\");", + " pm.expect(jsonData.payment_method_type).to.eql(\"giropay\");", " },", " );", "}", @@ -15484,7 +15406,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"pay_later\",\"payment_method_type\":\"affirm\",\"payment_experience\":\"redirect_to_url\",\"payment_method_data\":{\"pay_later\":{\"affirm_redirect\":{\"issuer_name\":\"affirm\",\"billing_email\":\"user-us@example.com\",\"billing_country\":\"US\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"giropay\",\"payment_method_data\":{\"bank_redirect\":{\"giropay\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -15630,7 +15552,7 @@ ] }, { - "name": "Scenario15-Bank Redirect-Ideal", + "name": "Scenario19-Bank Transfer-ach", "item": [ { "name": "Payments - Create", @@ -15735,7 +15657,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":800,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":800,\"customer_id\":\"poll\",\"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://hs-payments-test.netlify.app/payments\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -15832,21 +15754,32 @@ " );", "}", "", - "// Response body should have \"next_action.redirect_to_url\"", + "// Response body should have \"next_action.type\"", "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", + " \"[POST]::/payments - Content check if 'next_action.type' exists\",", " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", + " pm.expect(typeof jsonData.next_action.type !== \"undefined\").to.be.true;", " },", ");", "", - "// Response body should have value \"ideal\" for \"payment_method_type\"", + "// Response body should have value \"ach\" for \"payment_method_type\"", "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'ideal'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'ach'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"ideal\");", + " pm.expect(jsonData.payment_method_type).to.eql(\"ach\");", + " },", + " );", + "}", + "", + "// Response body should have value \"display_bank_transfer_information\" for \"next_action.type\"", + "if (jsonData?.next_action.type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'display_bank_transfer_information'\",", + " function () {", + " pm.expect(jsonData.next_action.type).to.eql(", + " \"display_bank_transfer_information\",", + " );", " },", " );", "}", @@ -15914,7 +15847,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_transfer\",\"payment_method_type\":\"ach\",\"payment_method_data\":{\"bank_transfer\":{\"ach_bank_transfer\":{\"billing_details\":{\"email\":\"johndoe@example.com\"}}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -16060,7 +15993,7 @@ ] }, { - "name": "Scenario16-Bank Redirect-sofort", + "name": "Scenario20-Bank Debit-ach", "item": [ { "name": "Payments - Create", @@ -16131,165 +16064,15 @@ " );", "}", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - " },", - " );", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Confirm", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", " },", " );", "}", - "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", - " },", - ");", - "", - "// Response body should have value \"sofort\" for \"payment_method_type\"", - "if (jsonData?.payment_method_type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'sofort'\",", - " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"sofort\");", - " },", - " );", - "}", - "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", - " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - " },", - " );", - "}", "" ], "type": "text/javascript" @@ -16306,26 +16089,6 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -16344,27 +16107,18 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"sofort\",\"payment_method_data\":{\"bank_redirect\":{\"sofort\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_noe_lb_fur_niederosterreich_u_wien\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"amount\":1800,\"currency\":\"USD\",\"confirm\":true,\"business_label\":\"default\",\"capture_method\":\"automatic\",\"connector\":[\"stripe\"],\"customer_id\":\"klarna\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"authentication_type\":\"three_ds\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"return_url\":\"https://google.com\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"setup_future_usage\":\"off_session\",\"business_country\":\"US\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"mandate_type\":{\"single_use\":{\"amount\":6540,\"currency\":\"USD\"}}},\"payment_method\":\"bank_debit\",\"payment_method_type\":\"ach\",\"payment_method_data\":{\"bank_debit\":{\"ach_bank_debit\":{\"billing_details\":{\"name\":\"John Doe\",\"email\":\"johndoe@example.com\"},\"account_number\":\"000123456789\",\"routing_number\":\"110000000\"}}},\"metadata\":{\"order_details\":{\"product_name\":\"Apple iphone 15\",\"quantity\":1,\"amount\":1800,\"account_name\":\"transaction_processing\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, @@ -16490,7 +16244,7 @@ ] }, { - "name": "Scenario17-Bank Redirect-eps", + "name": "Scenario21-Wallet-Wechatpay", "item": [ { "name": "Payments - Create", @@ -16595,7 +16349,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":800,\"currency\":\"USD\",\"confirm\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":800,\"customer_id\":\"poll\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -16692,21 +16446,30 @@ " );", "}", "", - "// Response body should have \"next_action.redirect_to_url\"", + "// Response body should have \"next_action.type\"", "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", + " \"[POST]::/payments - Content check if 'next_action.type' exists\",", " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", + " pm.expect(typeof jsonData.next_action.type !== \"undefined\").to.be.true;", " },", ");", "", - "// Response body should have value \"eps\" for \"payment_method_type\"", + "// Response body should have value \"ach\" for \"payment_method_type\"", "if (jsonData?.payment_method_type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'eps'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'we_chat_pay'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"eps\");", + " pm.expect(jsonData.payment_method_type).to.eql(\"we_chat_pay\");", + " },", + " );", + "}", + "", + "// Response body should have value \"qr_code_information\" for \"next_action.type\"", + "if (jsonData?.next_action.type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'qr_code_information'\",", + " function () {", + " pm.expect(jsonData.next_action.type).to.eql(\"qr_code_information\");", " },", " );", "}", @@ -16774,7 +16537,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"wallet\",\"payment_method_type\":\"we_chat_pay\",\"payment_method_data\":{\"wallet\":{\"we_chat_pay_qr\":{}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -16920,7 +16683,7 @@ ] }, { - "name": "Scenario18-Bank Redirect-giropay", + "name": "Scenario22- Update address and List Payment method", "item": [ { "name": "Payments - Create", @@ -16929,77 +16692,62 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx", + "// Validate status 2xx ", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Validate if response has JSON Body", + "// Validate if response has JSON Body ", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", + "try {jsonData = pm.response.json();}catch(e){}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", "", + "// pm.collectionVariables - Set customer_id as variable for jsonData.customer_id", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};", "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - " },", - " );", - "}", + "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\", function() {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + "})};", "" ], "type": "text/javascript" @@ -17025,7 +16773,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -17041,125 +16789,63 @@ "response": [] }, { - "name": "Payments - Confirm", + "name": "List Payment Methods for a Merchant", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx ", + "pm.test(\"[GET]::/payment_methods/:merchant_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "pm.test(\"[GET]::/payment_methods/:merchant_id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + "// Parse the response body as JSON", + "var responseBody = pm.response.json();", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"card\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'card'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"card\";", + " });", + "});", "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", - " },", - ");", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"pay_later\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'pay_later'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"pay_later\";", + " });", + "});", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"wallet\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'wallet'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"wallet\";", + " });", + "});", "", - "// Response body should have value \"giropay\" for \"payment_method_type\"", - "if (jsonData?.payment_method_type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'giropay'\",", - " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"giropay\");", - " },", - " );", - "}", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_debit\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_debit'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"bank_debit\";", + " });", + "});", "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", - " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - " },", - " );", - "}", - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_transfer\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_transfer'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"bank_transfer\";", + " });", + "});" ], "type": "text/javascript" } @@ -17186,126 +16872,84 @@ } ] }, - "method": "POST", + "method": "GET", "header": [ { - "key": "Content-Type", + "key": "Accept", "value": "application/json" }, { - "key": "Accept", - "value": "application/json" + "key": "x-feature", + "value": "router-custom", + "type": "text", + "disabled": true } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"giropay\",\"payment_method_data\":{\"bank_redirect\":{\"giropay\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" - }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" + "account", + "payment_methods" ], - "variable": [ + "query": [ { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "key": "client_secret", + "value": "{{client_secret}}" } ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To filter and list the applicable payment methods for a particular merchant id." }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Update", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + "pm.test(\"[POST]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + "// Parse the JSON response", + "var jsonData = pm.response.json();", "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + "// Check if the 'currency' is equal to \"EUR\"", + "pm.test(\"[POST]::/payments/:id -Content Check if 'currency' matches 'EUR' \", function () {", + " pm.expect(jsonData.currency).to.eql(\"EUR\");", + "});", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + "// Extract the \"country\" field from the JSON data", + "var country = jsonData.billing.address.country;", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", + "// Check if the country is \"NL\"", + "pm.test(\"[POST]::/payments/:id -Content Check if billing 'Country' matches NL (Netherlands)\", function () {", + " pm.expect(country).to.equal(\"NL\");", + "});", + "", + "var country1 = jsonData.shipping.address.country;", + "", + "// Check if the country is \"NL\"", + "pm.test(\"[POST]::/payments/:id -Content Check if shipping 'Country' matches NL (Netherlands)\", function () {", + " pm.expect(country1).to.equal(\"NL\");", + "});", "" ], "type": "text/javascript" @@ -17313,15 +16957,28 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"currency\":\"EUR\",\"shipping\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}},\"billing\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}}}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id", "host": [ "{{baseUrl}}" ], @@ -17329,144 +16986,117 @@ "payments", ":id" ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], "variable": [ { "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" + "value": "{{payment_id}}" } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created " }, "response": [] - } - ] - }, - { - "name": "Scenario19-Bank Transfer-ach", - "item": [ + }, { - "name": "Payments - Create", + "name": "List Payment Methods for a Merchant-copy", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx ", + "pm.test(\"[GET]::/payment_methods/:merchant_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "pm.test(\"[GET]::/payment_methods/:merchant_id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + "// Parse the response body as JSON", + "var responseBody = pm.response.json();", "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"card\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'card'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"card\";", + " });", + "});", "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"ideal\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id - Content Check if payment_method matches 'ideal'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"ideal\";", + " });", + "});", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - " },", - " );", - "}", - "" + "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_redirect\"", + "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_redirect'\", function () {", + " var paymentMethods = responseBody.payment_methods;", + " var cardPaymentMethod = paymentMethods.find(function (method) {", + " return method.payment_method == \"bank_redirect\";", + " });", + "});" ], "type": "text/javascript" } } ], "request": { - "method": "POST", + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "GET", "header": [ { - "key": "Content-Type", + "key": "Accept", "value": "application/json" }, { - "key": "Accept", - "value": "application/json" + "key": "x-feature", + "value": "router-custom", + "type": "text", + "disabled": true } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":800,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":800,\"customer_id\":\"poll\",\"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://hs-payments-test.netlify.app/payments\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" - }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", "host": [ "{{baseUrl}}" ], "path": [ - "payments" + "account", + "payment_methods" + ], + "query": [ + { + "key": "client_secret", + "value": "{{client_secret}}" + } ] }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "description": "To filter and list the applicable payment methods for a particular merchant id." }, "response": [] }, @@ -17477,156 +17107,1602 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx", + "// Validate status 2xx ", "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", + "pm.test(\"[POST]::/payments/:id/confirm - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", "", - "// Validate if response has JSON Body", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// Validate if response has JSON Body ", "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", + "});", + "", + "//// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.status) {", + "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\", function() {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + "})};", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"Example\",\"email\":\"guest@example.com\"},\"bank_name\":\"ing\"}}},\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"125.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"multi_use\":{\"amount\":7000,\"currency\":\"USD\",\"start_date\":\"2023-04-21T00:00:00Z\",\"end_date\":\"2023-05-21T00:00:00Z\",\"metadata\":{\"frequency\":\"13\"}}}},\"setup_future_usage\":\"off_session\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", + "try {jsonData = pm.response.json();}catch(e){}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", "", "// Response body should have value \"requires_customer_action\" for \"status\"", "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", + "pm.test(\"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\", function() {", + " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + "})};" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario23- Update Amount", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", "", - "// Response body should have \"next_action.type\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.type' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.type !== \"undefined\").to.be.true;", - " },", - ");", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", "", - "// Response body should have value \"ach\" for \"payment_method_type\"", - "if (jsonData?.payment_method_type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'ach'\",", - " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"ach\");", - " },", - " );", - "}", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", "", - "// Response body should have value \"display_bank_transfer_information\" for \"next_action.type\"", - "if (jsonData?.next_action.type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'display_bank_transfer_information'\",", - " function () {", - " pm.expect(jsonData.next_action.type).to.eql(", - " \"display_bank_transfer_information\",", - " );", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", + "", + "// pm.collectionVariables - Set customer_id as variable for jsonData.customer_id", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\", function() {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + "})};", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Update", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "", + "// Parse the JSON response", + "var jsonData = pm.response.json();", + "", + "// Check if the 'amount' is equal to \"1000\"", + "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", + " pm.expect(jsonData.amount).to.eql(1000);", + "});", + "", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":1000}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}" + } + ] + }, + "description": "To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created " + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments/:id/confirm - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "//// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\", function() {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + "})};", + "", + "", + "// Check if the 'amount' is equal to \"1000\"", + "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", + " pm.expect(jsonData.amount).to.eql(1000);", + "});", + "", + "//// Response body should have value \"amount_received\" for \"1000\"", + "if (jsonData?.amount_received) {", + "pm.test(\"[POST]::/payments - Content check if value for 'amount_received' matches '1000'\", function() {", + " pm.expect(jsonData.amount_received).to.eql(1000);", + "})};", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "x-feature", + "value": "router-custom", + "type": "text", + "disabled": true + }, + { + "key": "publishable_key", + "value": "", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"setup_future_usage\":\"off_session\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", + "", + "//// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\", function() {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + "})};", + "", + "", + "// Check if the 'amount' is equal to \"1000\"", + "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", + " pm.expect(jsonData.amount).to.eql(1000);", + "});", + "", + "//// Response body should have value \"amount_received\" for \"1000\"", + "if (jsonData?.amount_received) {", + "pm.test(\"[POST]::/payments - Content check if value for 'amount_received' matches '1000'\", function() {", + " pm.expect(jsonData.amount_received).to.eql(1000);", + "})};", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario24-Add card flow", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", + "", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(\"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\", function() {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " })};" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"stripesavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4111111111111111\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "List payment methods for a Customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "if (jsonData?.customer_payment_methods[0]?.payment_token) {", + " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", + " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "customers", + ":customer_id", + "payment_methods" + ], + "query": [ + { + "key": "accepted_country", + "value": "co", + "disabled": true + }, + { + "key": "accepted_country", + "value": "pa", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "voluptate ea", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "exercitation", + "disabled": true + }, + { + "key": "minimum_amount", + "value": "100", + "disabled": true + }, + { + "key": "maximum_amount", + "value": "10000000", + "disabled": true + }, + { + "key": "recurring_payment_enabled", + "value": "true", + "disabled": true + }, + { + "key": "installment_payment_enabled", + "value": "true", + "disabled": true + } + ], + "variable": [ + { + "key": "customer_id", + "value": "{{customer_id}}", + "description": "//Pass the customer id" + } + ] + }, + "description": "To filter and list the applicable payment methods for a particular Customer ID" + }, + "response": [] + }, + { + "name": "Save card payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", + "", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Save card payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "", + "// Response body should have value \"stripe\" for \"connector\"", + "if (jsonData?.connector) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", + " function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", " },", " );", "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\",\"card_cvc\":\"737\"}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + }, + { + "name": "Refunds - Create Copy", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":600,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + }, + { + "name": "Refunds - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/refunds/:id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" + } + ] + }, + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario25-Don't Pass CVV for save card flow and verifysuccess payment", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", + "", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"stripesavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"371449635398431\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"7373\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "List payment methods for a Customer", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "if (jsonData?.customer_payment_methods[0]?.payment_token) {", + " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", + " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "customers", + ":customer_id", + "payment_methods" + ], + "query": [ + { + "key": "accepted_country", + "value": "co", + "disabled": true + }, + { + "key": "accepted_country", + "value": "pa", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "voluptate ea", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "exercitation", + "disabled": true + }, + { + "key": "minimum_amount", + "value": "100", + "disabled": true + }, + { + "key": "maximum_amount", + "value": "10000000", + "disabled": true + }, + { + "key": "recurring_payment_enabled", + "value": "true", + "disabled": true + }, + { + "key": "installment_payment_enabled", + "value": "true", + "disabled": true + } + ], + "variable": [ + { + "key": "customer_id", + "value": "{{customer_id}}", + "description": "//Pass the customer id" + } + ] + }, + "description": "To filter and list the applicable payment methods for a particular Customer ID" + }, + "response": [] + }, + { + "name": "Save card payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Validate if response has JSON Body ", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + "} else {", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", - " function () {", - " pm.expect(jsonData.connector).to.eql(\"stripe\");", - " },", - " );", - "}", - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};" ], "type": "text/javascript" } } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -17645,51 +18721,45 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_transfer\",\"payment_method_type\":\"ach\",\"payment_method_data\":{\"bank_transfer\":{\"ach_bank_transfer\":{\"billing_details\":{\"email\":\"johndoe@example.com\"}}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, { - "name": "Payments - Retrieve", + "name": "Save card payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -17738,43 +18808,76 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", + "// Response body should have value \"adyen\" for \"connector\"", + "if (jsonData?.connector) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'stripe'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", " },", " );", "}", - "" + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(\"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\", function() {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " })};" ], "type": "text/javascript" } } ], "request": { - "method": "GET", + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "confirm" ], "variable": [ { @@ -17784,14 +18887,14 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] } ] }, { - "name": "Scenario19-Bank Debit-ach", + "name": "Scenario26-Save card payment with manual capture", "item": [ { "name": "Payments - Create", @@ -17800,87 +18903,66 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx", + "// Validate status 2xx ", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Validate if response has JSON Body", + "// Validate if response has JSON Body ", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", + "try {jsonData = pm.response.json();}catch(e){}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};", + "", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", - "}", - "" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" + "}" ], "type": "text/javascript" } @@ -17905,7 +18987,7 @@ "language": "json" } }, - "raw": "{\"amount\":1800,\"currency\":\"USD\",\"confirm\":true,\"business_label\":\"default\",\"capture_method\":\"automatic\",\"connector\":[\"stripe\"],\"customer_id\":\"klarna\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"authentication_type\":\"three_ds\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"return_url\":\"https://google.com\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"setup_future_usage\":\"off_session\",\"business_country\":\"US\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"mandate_type\":{\"single_use\":{\"amount\":6540,\"currency\":\"USD\"}}},\"payment_method\":\"bank_debit\",\"payment_method_type\":\"ach\",\"payment_method_data\":{\"bank_debit\":{\"ach_bank_debit\":{\"billing_details\":{\"name\":\"John Doe\",\"email\":\"johndoe@example.com\"},\"account_number\":\"000123456789\",\"routing_number\":\"110000000\"}}},\"metadata\":{\"order_details\":{\"product_name\":\"Apple iphone 15\",\"quantity\":1,\"amount\":1800,\"account_name\":\"transaction_processing\"}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"adyensavecard_{{random_number}}\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"371449635398431\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"7373\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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", @@ -17983,18 +19065,53 @@ " \"- use {{client_secret}} as collection variable for value\",", " jsonData.client_secret,", " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"Succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Validate the connector", + "pm.test(\"[POST]::/payments - connector\", function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", + "});", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6540);", + " },", " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", + "// Response body should have value \"6540\" for \"amount_capturable\"", + "if (jsonData?.amount) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.amount_capturable).to.eql(0);", " },", " );", "}", @@ -18038,91 +19155,168 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - } - ] - }, - { - "name": "Scenario22-Wallet-Wechatpay", - "item": [ + }, { - "name": "Payments - Create", + "name": "List payment methods for a Customer", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", + "// Validate status 2xx ", + "pm.test(\"[GET]::/payment_methods/:customer_id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payment_methods/:customer_id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {jsonData = pm.response.json();}catch(e){}", + "", + "if (jsonData?.customer_payment_methods[0]?.payment_token) {", + " pm.collectionVariables.set(\"payment_token\", jsonData.customer_payment_methods[0].payment_token);", + " console.log(\"- use {{payment_token}} as collection variable for value\", jsonData.customer_payment_methods[0].payment_token);", + "} else {", + " console.log('INFO - Unable to assign variable {{payment_token}}, as jsonData.customer_payment_methods[0].payment_token is undefined.');", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/customers/:customer_id/payment_methods", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "customers", + ":customer_id", + "payment_methods" + ], + "query": [ + { + "key": "accepted_country", + "value": "co", + "disabled": true + }, + { + "key": "accepted_country", + "value": "pa", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "voluptate ea", + "disabled": true + }, + { + "key": "accepted_currency", + "value": "exercitation", + "disabled": true + }, + { + "key": "minimum_amount", + "value": "100", + "disabled": true + }, + { + "key": "maximum_amount", + "value": "10000000", + "disabled": true + }, + { + "key": "recurring_payment_enabled", + "value": "true", + "disabled": true + }, + { + "key": "installment_payment_enabled", + "value": "true", + "disabled": true + } + ], + "variable": [ + { + "key": "customer_id", + "value": "{{customer_id}}", + "description": "//Pass the customer id" + } + ] + }, + "description": "To filter and list the applicable payment methods for a particular Customer ID" + }, + "response": [] + }, + { + "name": "Save card payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx ", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", "});", "", - "// Validate if response has JSON Body", + "// Validate if response has JSON Body ", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", + "try {jsonData = pm.response.json();}catch(e){}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", + "};", + "", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", + "};", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", + " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", + "};", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - " },", - " );", - "}", - "" + "if (jsonData?.customer_id) {", + " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", + " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", + "} else {", + " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", + "};" ], "type": "text/javascript" } @@ -18147,7 +19341,7 @@ "language": "json" } }, - "raw": "{\"amount\":800,\"currency\":\"USD\",\"confirm\":false,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":800,\"customer_id\":\"poll\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"statement_descriptor_name\":\"Juspay\",\"statement_descriptor_suffix\":\"Router\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"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://google.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"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", @@ -18163,7 +19357,7 @@ "response": [] }, { - "name": "Payments - Confirm", + "name": "Save card payments - Confirm", "event": [ { "listen": "test", @@ -18234,43 +19428,16 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"requires_capture\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", - "", - "// Response body should have \"next_action.type\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.type' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.type !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have value \"ach\" for \"payment_method_type\"", - "if (jsonData?.payment_method_type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'we_chat_pay'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_capture'\",", " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"we_chat_pay\");", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", " },", " );", "}", "", - "// Response body should have value \"qr_code_information\" for \"next_action.type\"", - "if (jsonData?.next_action.type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'qr_code_information'\",", - " function () {", - " pm.expect(jsonData.next_action.type).to.eql(\"qr_code_information\");", - " },", - " );", - "}", "", "// Response body should have value \"stripe\" for \"connector\"", "if (jsonData?.connector) {", @@ -18285,15 +19452,6 @@ ], "type": "text/javascript" } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "" - ], - "type": "text/javascript" - } } ], "request": { @@ -18335,7 +19493,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"wallet\",\"payment_method_type\":\"we_chat_pay\",\"payment_method_data\":{\"wallet\":{\"we_chat_pay_qr\":{}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_token\":\"{{payment_token}}\",\"card_cvc\":\"7373\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -18360,26 +19518,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -18428,12 +19589,47 @@ " );", "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Validate the connector", + "pm.test(\"[POST]::/payments - connector\", function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", + "});", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount_capturable\"", + "if (jsonData?.amount_capturable) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", + " function () {", + " pm.expect(jsonData.amount_capturable).to.eql(6540);", " },", " );", "}", @@ -18444,27 +19640,35 @@ } ], "request": { - "method": "GET", + "method": "POST", "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, { "key": "Accept", "value": "application/json" } ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount_to_capture\":6540,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + }, "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } + ":id", + "capture" ], "variable": [ { @@ -18474,288 +19678,225 @@ } ] }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + "description": "To capture the funds for an uncaptured payment" }, "response": [] - } - ] - }, - { - "name": "Scenario22- Update address and List Payment method", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Retrieve-copy", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "// pm.collectionVariables - Set customer_id as variable for jsonData.customer_id", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};", - "// Response body should have value \"requires_payment_method\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\", function() {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - "})};", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "List Payment Methods for a Merchant", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx ", - "pm.test(\"[GET]::/payment_methods/:merchant_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payment_methods/:merchant_id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Parse the response body as JSON", - "var responseBody = pm.response.json();", - "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"card\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'card'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"card\";", - " });", - "});", - "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"pay_later\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'pay_later'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"pay_later\";", - " });", - "});", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"wallet\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'wallet'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"wallet\";", - " });", - "});", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_debit\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_debit'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"bank_debit\";", - " });", + "// Validate the connector", + "pm.test(\"[POST]::/payments - connector\", function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", "});", "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_transfer\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_transfer'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"bank_transfer\";", - " });", - "});" + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6000\" for \"amount_received\"", + "if (jsonData?.amount_received) {", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6540);", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount_capturable\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount_capturable' matches 'amount - 540'\",", + " function () {", + " pm.expect(jsonData.amount_capturable).to.eql(0);", + " },", + " );", + "}", + "" ], "type": "text/javascript" } } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "GET", "header": [ { "key": "Accept", "value": "application/json" - }, - { - "key": "x-feature", - "value": "router-custom", - "type": "text", - "disabled": true } ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "account", - "payment_methods" + "payments", + ":id" ], "query": [ { - "key": "client_secret", - "value": "{{client_secret}}" + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "To filter and list the applicable payment methods for a particular merchant id." + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Update", + "name": "Refunds - Create Copy", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[POST]::/refunds - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "// Parse the JSON response", - "var jsonData = pm.response.json();", - "", - "// Check if the 'currency' is equal to \"EUR\"", - "pm.test(\"[POST]::/payments/:id -Content Check if 'currency' matches 'EUR' \", function () {", - " pm.expect(jsonData.currency).to.eql(\"EUR\");", - "});", - "", - "// Extract the \"country\" field from the JSON data", - "var country = jsonData.billing.address.country;", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", "", - "// Check if the country is \"NL\"", - "pm.test(\"[POST]::/payments/:id -Content Check if billing 'Country' matches NL (Netherlands)\", function () {", - " pm.expect(country).to.equal(\"NL\");", - "});", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", "", - "var country1 = jsonData.shipping.address.country;", + "// Response body should have value \"540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(540);", + " },", + " );", + "}", "", - "// Check if the country is \"NL\"", - "pm.test(\"[POST]::/payments/:id -Content Check if shipping 'Country' matches NL (Netherlands)\", function () {", - " pm.expect(country1).to.equal(\"NL\");", + "// Validate the connector", + "pm.test(\"[POST]::/payments - connector\", function () {", + " pm.expect(jsonData.connector).to.eql(\"stripe\");", "});", "" ], @@ -18782,172 +19923,205 @@ "language": "json" } }, - "raw": "{\"currency\":\"EUR\",\"shipping\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}},\"billing\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id", + "raw": "{{baseUrl}}/refunds", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}" - } + "refunds" ] }, - "description": "To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created " + "description": "To create a refund against an already processed payment" }, "response": [] }, { - "name": "List Payment Methods for a Merchant-copy", + "name": "Refunds - Retrieve Copy", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[GET]::/payment_methods/:merchant_id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[GET]::/refunds/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payment_methods/:merchant_id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "pm.test(\"[GET]::/refunds/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "// Parse the response body as JSON", - "var responseBody = pm.response.json();", - "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"card\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'card'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"card\";", - " });", - "});", - "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"ideal\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id - Content Check if payment_method matches 'ideal'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"ideal\";", - " });", - "});", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", "", - "// Check if \"payment_methods\" array contains a \"payment_method\" with the value \"bank_redirect\"", - "pm.test(\"[GET]::/payment_methods/:merchant_id -Content Check if payment_method matches 'bank_redirect'\", function () {", - " var paymentMethods = responseBody.payment_methods;", - " var cardPaymentMethod = paymentMethods.find(function (method) {", - " return method.payment_method == \"bank_redirect\";", - " });", - "});" + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/refunds - Content check if value for 'amount' matches '540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(540);", + " },", + " );", + "}", + "" ], "type": "text/javascript" } } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "GET", "header": [ { "key": "Accept", "value": "application/json" - }, - { - "key": "x-feature", - "value": "router-custom", - "type": "text", - "disabled": true } ], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "{{baseUrl}}/account/payment_methods?client_secret={{client_secret}}", + "raw": "{{baseUrl}}/refunds/:id", "host": [ "{{baseUrl}}" ], "path": [ - "account", - "payment_methods" + "refunds", + ":id" ], - "query": [ + "variable": [ { - "key": "client_secret", - "value": "{{client_secret}}" + "key": "id", + "value": "{{refund_id}}", + "description": "(Required) unique refund id" } ] }, - "description": "To filter and list the applicable payment methods for a particular merchant id." + "description": "To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] - }, + } + ] + }, + { + "name": "Scenario27-Create payment without customer_id and with billing address and shipping address", + "item": [ { - "name": "Payments - Confirm", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments/:id/confirm - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", - "//// Response body should have value \"requires_customer_action\" for \"status\"", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_customer_action'\", function() {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - "})};", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", "", + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", "" ], "type": "text/javascript" @@ -18955,26 +20129,6 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -18993,26 +20147,18 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"Example\",\"email\":\"guest@example.com\"},\"bank_name\":\"ing\"}}},\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"125.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"multi_use\":{\"amount\":7000,\"currency\":\"USD\",\"start_date\":\"2023-04-21T00:00:00Z\",\"end_date\":\"2023-05-21T00:00:00Z\",\"metadata\":{\"frequency\":\"13\"}}}},\"setup_future_usage\":\"off_session\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"24\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}" - } + "payments" ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] }, @@ -19023,55 +20169,87 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"Succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", "", - "// Response body should have value \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\", function() {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - "})};" + "// Response body should have \"connector_transaction_id\"", + "pm.test(", + " \"[POST]::/payments - Content check if 'connector_transaction_id' exists\",", + " function () {", + " pm.expect(typeof jsonData.connector_transaction_id !== \"undefined\").to.be", + " .true;", + " },", + ");", + "" ], "type": "text/javascript" } @@ -19113,9 +20291,14 @@ "response": [] } ] - }, + } + ] + }, + { + "name": "Variation Cases", + "item": [ { - "name": "Scenario23- Update Amount", + "name": "Scenario10-Refund exceeds amount captured", "item": [ { "name": "Payments - Create", @@ -19124,62 +20307,77 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "// pm.collectionVariables - Set customer_id as variable for jsonData.customer_id", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(\"- use {{customer_id}} as collection variable for value\",jsonData.customer_id);", - "} else {", - " console.log('INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.');", - "};", - "// Response body should have value \"requires_payment_method\" for \"status\"", + "// Response body should have value \"requires_capture\" for \"status\"", "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\", function() {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - "})};", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_capture'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_capture\");", + " },", + " );", + "}", "" ], "type": "text/javascript" @@ -19205,7 +20403,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"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", @@ -19221,127 +20419,106 @@ "response": [] }, { - "name": "Payments - Update", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", - "});", - "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "", - "// Parse the JSON response", - "var jsonData = pm.response.json();", - "", - "// Check if the 'amount' is equal to \"1000\"", - "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", - " pm.expect(jsonData.amount).to.eql(1000);", - "});", - "", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":1000}" - }, - "url": { - "raw": "{{baseUrl}}/payments/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}" - } - ] - }, - "description": "To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created " - }, - "response": [] - }, - { - "name": "Payments - Confirm", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx ", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments/:id/confirm - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", - "// Validate if response has JSON Body ", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", - "//// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\", function() {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - "})};", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " },", + " );", + "}", "", - "// Check if the 'amount' is equal to \"1000\"", - "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", - " pm.expect(jsonData.amount).to.eql(1000);", - "});", + "// Response body should have value \"6540\" for \"amount\"", + "if (jsonData?.amount) {", + " pm.test(", + " \"[post]:://payments/:id/capture - Content check if value for 'amount' matches '6540'\",", + " function () {", + " pm.expect(jsonData.amount).to.eql(6540);", + " },", + " );", + "}", "", - "//// Response body should have value \"amount_received\" for \"1000\"", + "// Response body should have value \"6000\" for \"amount_received\"", "if (jsonData?.amount_received) {", - "pm.test(\"[POST]::/payments - Content check if value for 'amount_received' matches '1000'\", function() {", - " pm.expect(jsonData.amount_received).to.eql(1000);", - "})};", + " pm.test(", + " \"[POST]::/payments:id/capture - Content check if value for 'amount_received' matches '6000'\",", + " function () {", + " pm.expect(jsonData.amount_received).to.eql(6000);", + " },", + " );", + "}", "" ], "type": "text/javascript" @@ -19349,26 +20526,6 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -19378,18 +20535,6 @@ { "key": "Accept", "value": "application/json" - }, - { - "key": "x-feature", - "value": "router-custom", - "type": "text", - "disabled": true - }, - { - "key": "publishable_key", - "value": "", - "type": "text", - "disabled": true } ], "body": { @@ -19399,26 +20544,27 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"setup_future_usage\":\"off_session\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" + "raw": "{\"amount_to_capture\":6000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ "payments", ":id", - "confirm" + "capture" ], "variable": [ { "key": "id", - "value": "{{payment_id}}" + "value": "{{payment_id}}", + "description": "(Required) unique payment id" } ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To capture the funds for an uncaptured payment" }, "response": [] }, @@ -19429,67 +20575,77 @@ "listen": "test", "script": { "exec": [ - "// Validate status 2xx ", + "// Validate status 2xx", "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", "});", "", - "// Validate if response has JSON Body ", + "// Validate if response has JSON Body", "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", + " pm.response.to.have.jsonBody();", "});", "", "// Set response object as internal variable", "let jsonData = {};", - "try {jsonData = pm.response.json();}catch(e){}", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", "", "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(\"- use {{payment_id}} as collection variable for value\",jsonData.payment_id);", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.');", - "};", - "", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(\"- use {{mandate_id}} as collection variable for value\",jsonData.mandate_id);", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", "", "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(\"- use {{client_secret}} as collection variable for value\",jsonData.client_secret);", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", "} else {", - " console.log('INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.');", - "};", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", "", - "//// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", - "pm.test(\"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\", function() {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - "})};", - "", - "", - "// Check if the 'amount' is equal to \"1000\"", - "pm.test(\"[POST]::/payments/:id -Content Check if 'amount' matches '1000' \", function () {", - " pm.expect(jsonData.amount).to.eql(1000);", - "});", - "", - "//// Response body should have value \"amount_received\" for \"1000\"", - "if (jsonData?.amount_received) {", - "pm.test(\"[POST]::/payments - Content check if value for 'amount_received' matches '1000'\", function() {", - " pm.expect(jsonData.amount_received).to.eql(1000);", - "})};", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'partially_captured'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", + " },", + " );", + "}", "" ], "type": "text/javascript" @@ -19530,14 +20686,114 @@ "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] + }, + { + "name": "Refunds - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 4xx", + "pm.test(\"[POST]::/refunds - Status code is 4xx\", function () {", + " pm.response.to.be.error;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have \"error\"", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if 'error' exists\",", + " function () {", + " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have value \"invalid_request\" for \"error type\"", + "if (jsonData?.error?.type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error.type' matches 'invalid_request'\",", + " function () {", + " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", + " },", + " );", + "}", + "", + "// Response body should have value \"The refund amount exceeds the amount captured\" for \"error message\"", + "if (jsonData?.error?.type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error.message' matches 'The refund amount exceeds the amount captured'\",", + " function () {", + " pm.expect(jsonData.error.message).to.eql(\"The refund amount exceeds the amount captured\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":6540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] } ] - } - ] - }, - { - "name": "Variation Cases", - "item": [ + }, { "name": "Scenario1-Create payment with Invalid card details", "item": [ diff --git a/postman/collection-json/trustpay.postman_collection.json b/postman/collection-json/trustpay.postman_collection.json index b88fa9e110c..cacc015851d 100644 --- a/postman/collection-json/trustpay.postman_collection.json +++ b/postman/collection-json/trustpay.postman_collection.json @@ -811,7 +811,7 @@ "name": "Happy Cases", "item": [ { - "name": "Scenario1-Create payment with confirm true", + "name": "Scenario2a-Create payment with confirm false card holder name empty", "item": [ { "name": "Payments - Create", @@ -882,12 +882,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", @@ -916,7 +916,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"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\",\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -931,6 +931,155 @@ }, "response": [] }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, { "name": "Payments - Retrieve", "event": [ @@ -1000,16 +1149,15 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", - "}", - "" + "}" ], "type": "text/javascript" } @@ -1053,7 +1201,7 @@ ] }, { - "name": "Scenario2-Create payment with confirm false", + "name": "Scenario2b-Create payment with confirm false card holder name null", "item": [ { "name": "Payments - Create", @@ -1124,12 +1272,12 @@ " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", @@ -1158,7 +1306,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"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\"}}" + "raw": "{\"amount\":6540,\"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\",\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -1299,7 +1447,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":null,\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -2303,6 +2451,15 @@ ], "type": "text/javascript" } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } } ], "request": { @@ -2344,7 +2501,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"return_url\":\"https://integ.hyperswitch.io/home\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"5200000000000015\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"\",\"card_cvc\":\"737\",\"card_issuer\":\"\",\"card_network\":\"Visa\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36\",\"accept_header\":\"text\\\\/html,application\\\\/xhtml+xml,application\\\\/xml;q=0.9,image\\\\/webp,image\\\\/apng,*\\\\/*;q=0.8\",\"language\":\"en-GB\",\"color_depth\":30,\"ip_address\":\"65.1.52.138\",\"screen_height\":1117,\"screen_width\":1728,\"time_zone\":-330,\"java_enabled\":true,\"java_script_enabled\":true}}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"return_url\":\"https://integ.hyperswitch.io/home\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"5200000000000015\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"John Doe\",\"card_cvc\":\"737\",\"card_issuer\":\"\",\"card_network\":\"Visa\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36\",\"accept_header\":\"text\\\\/html,application\\\\/xhtml+xml,application\\\\/xml;q=0.9,image\\\\/webp,image\\\\/apng,*\\\\/*;q=0.8\",\"language\":\"en-GB\",\"color_depth\":30,\"ip_address\":\"65.1.52.138\",\"screen_height\":1117,\"screen_width\":1728,\"time_zone\":-330,\"java_enabled\":true,\"java_script_enabled\":true}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -2922,7 +3079,7 @@ ] }, { - "name": "Scenario8-Bank Redirect-Ideal", + "name": "Scenario7-Bank Redirect-Ideal", "item": [ { "name": "Payments - Create", @@ -3344,7 +3501,7 @@ ] }, { - "name": "Scenario11-Bank Redirect-giropay", + "name": "Scenario8-Bank Redirect-giropay", "item": [ { "name": "Payments - Create", @@ -3764,25 +3921,20 @@ "response": [] } ] - } - ] - }, - { - "name": "Variation Cases", - "item": [ + }, { - "name": "Scenario1-Create payment with Invalid card details", + "name": "Scenario1-Create payment with confirm true", "item": [ { - "name": "Payments - Create(Invalid card number)", + "name": "Payments - Create", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 4xx", - "pm.test(\"[POST]::/payments - Status code is 4xx\", function () {", - " pm.response.to.be.error;", + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", @@ -3842,17 +3994,12 @@ " );", "}", "", - "// Response body should have \"error\"", - "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", - " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", - "});", - "", - "// Response body should have value \"connector error\" for \"error type\"", - "if (jsonData?.error?.type) {", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.type' matches 'connector_error'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.error.type).to.eql(\"connector_error\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3881,7 +4028,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"12345\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3897,26 +4044,26 @@ "response": [] }, { - "name": "Payments - Create(Invalid Exp month)", + "name": "Payments - Retrieve", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 4xx", - "pm.test(\"[POST]::/payments - Status code is 4xx\", function () {", - " pm.response.to.be.error;", + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", " );", "});", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -3965,17 +4112,12 @@ " );", "}", "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", - " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", - "});", - "", - "// Response body should have value \"connector error\" for \"error type\"", - "if (jsonData?.error?.type) {", + "// Response body should have value \"Succeeded\" for \"status\"", + "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3986,41 +4128,799 @@ } ], "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Accept", "value": "application/json" } ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"19\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" - }, "url": { - "raw": "{{baseUrl}}/payments", + "raw": "{{baseUrl}}/payments/:id?force_sync=true", "host": [ "{{baseUrl}}" ], "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario2-Create payment with confirm false", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_payment_method\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":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\",\"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\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Confirm", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"Joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments/:id/confirm", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id", + "confirm" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"succeeded\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" + }, + "response": [] + } + ] + } + ] + }, + { + "name": "Variation Cases", + "item": [ + { + "name": "Scenario5-Refund for unsuccessful payment", + "item": [ + { + "name": "Payments - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_confirmation\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" + }, + "response": [] + }, + { + "name": "Payments - Retrieve", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 2xx", + "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", + " pm.response.to.be.success;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Validate if response has JSON Body", + "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + " pm.response.to.have.jsonBody();", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", + "if (jsonData?.payment_id) {", + " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", + " console.log(", + " \"- use {{payment_id}} as collection variable for value\",", + " jsonData.payment_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", + "if (jsonData?.mandate_id) {", + " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", + " console.log(", + " \"- use {{mandate_id}} as collection variable for value\",", + " jsonData.mandate_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", + " );", + "}", + "", + "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", + "if (jsonData?.client_secret) {", + " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", + " console.log(", + " \"- use {{client_secret}} as collection variable for value\",", + " jsonData.client_secret,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", + " );", + "}", + "", + "// Response body should have value \"requires_confirmation\" for \"status\"", + "if (jsonData?.status) {", + " pm.test(", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_confirmation'\",", + " function () {", + " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/payments/:id?force_sync=true", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments", + ":id" + ], + "query": [ + { + "key": "force_sync", + "value": "true" + } + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } + ] + }, + "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" }, "response": [] }, { - "name": "Payments - Create(Invalid", + "name": "Refunds - Create", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// Validate status 4xx", + "pm.test(\"[POST]::/refunds - Status code is 4xx\", function () {", + " pm.response.to.be.error;", + "});", + "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", + "// Set response object as internal variable", + "let jsonData = {};", + "try {", + " jsonData = pm.response.json();", + "} catch (e) {}", + "", + "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", + "if (jsonData?.refund_id) {", + " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", + " console.log(", + " \"- use {{refund_id}} as collection variable for value\",", + " jsonData.refund_id,", + " );", + "} else {", + " console.log(", + " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", + " );", + "}", + "", + "// Response body should have \"error\"", + "pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if 'error' exists\",", + " function () {", + " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", + " },", + ");", + "", + "// Response body should have value \"invalid_request\" for \"error type\"", + "if (jsonData?.error?.type) {", + " pm.test(", + " \"[POST]::/payments/:id/confirm - Content check if value for 'error.type' matches 'invalid_request'\",", + " function () {", + " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", + " },", + " );", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + }, + "url": { + "raw": "{{baseUrl}}/refunds", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "refunds" + ] + }, + "description": "To create a refund against an already processed payment" + }, + "response": [] + } + ] + }, + { + "name": "Scenario1-Create payment with Invalid card details", + "item": [ + { + "name": "Payments - Create(Invalid card number)", "event": [ { "listen": "test", @@ -4088,7 +4988,7 @@ " );", "}", "", - "// Response body should have \"next_action.redirect_to_url\"", + "// Response body should have \"error\"", "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", "});", @@ -4096,9 +4996,9 @@ "// Response body should have value \"connector error\" for \"error type\"", "if (jsonData?.error?.type) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'\",", + " \"[POST]::/payments - Content check if value for 'error.type' matches 'connector_error'\",", " function () {", - " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", + " pm.expect(jsonData.error.type).to.eql(\"connector_error\");", " },", " );", "}", @@ -4127,7 +5027,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"12\",\"card_exp_year\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"12345\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4143,7 +5043,7 @@ "response": [] }, { - "name": "Payments - Create(invalid CVV)", + "name": "Payments - Create(Invalid Exp month)", "event": [ { "listen": "test", @@ -4211,7 +5111,7 @@ " );", "}", "", - "// Response body should have \"error\"", + "// Response body should have \"next_action.redirect_to_url\"", "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", "});", @@ -4250,7 +5150,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"12\",\"card_exp_year\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"1234\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"19\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4264,22 +5164,17 @@ "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] - } - ] - }, - { - "name": "Scenario2-Confirming the payment without PMD", - "item": [ + }, { - "name": "Payments - Create", + "name": "Payments - Create(Invalid", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 4xx", + "pm.test(\"[POST]::/payments - Status code is 4xx\", function () {", + " pm.response.to.be.error;", "});", "", "// Validate if response header has matching content-type", @@ -4339,12 +5234,17 @@ " );", "}", "", - "// Response body should have value \"requires_payment_method\" for \"status\"", - "if (jsonData?.status) {", + "// Response body should have \"next_action.redirect_to_url\"", + "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", + " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", + "});", + "", + "// Response body should have value \"connector error\" for \"error type\"", + "if (jsonData?.error?.type) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", + " \"[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", + " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", " },", " );", "}", @@ -4373,7 +5273,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"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\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"12\",\"card_exp_year\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4389,7 +5289,7 @@ "response": [] }, { - "name": "Payments - Confirm", + "name": "Payments - Create(invalid CVV)", "event": [ { "listen": "test", @@ -4400,18 +5300,15 @@ " pm.response.to.be.error;", "});", "", - "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", - " function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - " },", - ");", - "", + "// Validate if response header has matching content-type", + "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + "});", + "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -4461,17 +5358,14 @@ "}", "", "// Response body should have \"error\"", - "pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if 'error' exists\",", - " function () {", - " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", - " },", - ");", + "pm.test(\"[POST]::/payments - Content check if 'error' exists\", function () {", + " pm.expect(typeof jsonData.error !== \"undefined\").to.be.true;", + "});", "", "// Response body should have value \"connector error\" for \"error type\"", "if (jsonData?.error?.type) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'error.type' matches 'invalid_request'\",", + " \"[POST]::/payments - Content check if value for 'error.type' matches 'invalid_request'\",", " function () {", " pm.expect(jsonData.error.type).to.eql(\"invalid_request\");", " },", @@ -4484,26 +5378,6 @@ } ], "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, "method": "POST", "header": [ { @@ -4522,34 +5396,25 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"12\",\"card_exp_year\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"1234\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"8056594427\",\"country_code\":\"+91\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", + "raw": "{{baseUrl}}/payments", "host": [ "{{baseUrl}}" ], "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } + "payments" ] }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" }, "response": [] } ] }, { - "name": "Scenario3-Capture the succeeded payment", + "name": "Scenario2-Confirming the payment without PMD", "item": [ { "name": "Payments - Create", @@ -4620,12 +5485,12 @@ " );", "}", "", - "// Response body should have value \"succeeded\" for \"status\"", + "// Response body should have value \"requires_payment_method\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", + " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", " },", " );", "}", @@ -4654,7 +5519,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"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\",\"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\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4670,20 +5535,20 @@ "response": [] }, { - "name": "Payments - Capture", + "name": "Payments - Confirm", "event": [ { "listen": "test", "script": { "exec": [ "// Validate status 4xx", - "pm.test(\"[POST]::/payments/:id/capture - Status code is 4xx\", function () {", + "pm.test(\"[POST]::/payments - Status code is 4xx\", function () {", " pm.response.to.be.error;", "});", "", "// Validate if response header has matching content-type", "pm.test(", - " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " \"[POST]::/payments/:id/confirm - Content-Type is application/json\",", " function () {", " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", " \"application/json\",", @@ -4692,7 +5557,7 @@ ");", "", "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/confirm - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -4765,6 +5630,26 @@ } ], "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{publishable_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, "method": "POST", "header": [ { @@ -4783,17 +5668,17 @@ "language": "json" } }, - "raw": "{\"amount_to_capture\":7000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" + "raw": "{\"client_secret\":\"{{client_secret}}\"}" }, "url": { - "raw": "{{baseUrl}}/payments/:id/capture", + "raw": "{{baseUrl}}/payments/:id/confirm", "host": [ "{{baseUrl}}" ], "path": [ "payments", ":id", - "capture" + "confirm" ], "variable": [ { @@ -4803,14 +5688,14 @@ } ] }, - "description": "To capture the funds for an uncaptured payment" + "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" }, "response": [] } ] }, { - "name": "Scenario4-Refund exceeds amount", + "name": "Scenario3-Capture the succeeded payment", "item": [ { "name": "Payments - Create", @@ -4931,26 +5816,29 @@ "response": [] }, { - "name": "Payments - Retrieve", + "name": "Payments - Capture", "event": [ { "listen": "test", "script": { "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", + "// Validate status 4xx", + "pm.test(\"[POST]::/payments/:id/capture - Status code is 4xx\", function () {", + " pm.response.to.be.error;", "});", "", "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", + "pm.test(", + " \"[POST]::/payments/:id/capture - Content-Type is application/json\",", + " function () {", + " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", + " \"application/json\",", + " );", + " },", + ");", "", "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", + "pm.test(\"[POST]::/payments/:id/capture - Response has JSON Body\", function () {", " pm.response.to.have.jsonBody();", "});", "", @@ -4999,94 +5887,6 @@ " );", "}", "", - "// Response body should have value \"Succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" - }, - "response": [] - }, - { - "name": "Refunds - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 4xx", - "pm.test(\"[POST]::/refunds - Status code is 4xx\", function () {", - " pm.response.to.be.error;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/refunds - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set refund_id as variable for jsonData.payment_id", - "if (jsonData?.refund_id) {", - " pm.collectionVariables.set(\"refund_id\", jsonData.refund_id);", - " console.log(", - " \"- use {{refund_id}} as collection variable for value\",", - " jsonData.refund_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{refund_id}}, as jsonData.refund_id is undefined.\",", - " );", - "}", - "", "// Response body should have \"error\"", "pm.test(", " \"[POST]::/payments/:id/confirm - Content check if 'error' exists\",", @@ -5129,25 +5929,34 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":7000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount_to_capture\":7000,\"statement_descriptor_name\":\"Joseph\",\"statement_descriptor_suffix\":\"JS\"}" }, "url": { - "raw": "{{baseUrl}}/refunds", + "raw": "{{baseUrl}}/payments/:id/capture", "host": [ "{{baseUrl}}" ], "path": [ - "refunds" + "payments", + ":id", + "capture" + ], + "variable": [ + { + "key": "id", + "value": "{{payment_id}}", + "description": "(Required) unique payment id" + } ] }, - "description": "To create a refund against an already processed payment" + "description": "To capture the funds for an uncaptured payment" }, "response": [] } ] }, { - "name": "Scenario8-Refund for unsuccessful payment", + "name": "Scenario4-Refund exceeds amount", "item": [ { "name": "Payments - Create", @@ -5218,12 +6027,12 @@ " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// Response body should have value \"succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_confirmation'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -5252,7 +6061,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"5100000000000511\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"PiX\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -5336,12 +6145,12 @@ " );", "}", "", - "// Response body should have value \"requires_confirmation\" for \"status\"", + "// Response body should have value \"Succeeded\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'requires_confirmation'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_confirmation\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -5432,7 +6241,7 @@ " },", ");", "", - "// Response body should have value \"invalid_request\" for \"error type\"", + "// Response body should have value \"connector error\" for \"error type\"", "if (jsonData?.error?.type) {", " pm.test(", " \"[POST]::/payments/:id/confirm - Content check if value for 'error.type' matches 'invalid_request'\",", @@ -5466,7 +6275,7 @@ "language": "json" } }, - "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":540,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"payment_id\":\"{{payment_id}}\",\"amount\":7000,\"reason\":\"Customer returned product\",\"refund_type\":\"instant\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/refunds", diff --git a/postman/portman-config.json b/postman/portman-config.json index 599767fd6f1..7f94c31a521 100644 --- a/postman/portman-config.json +++ b/postman/portman-config.json @@ -1583,7 +1583,7 @@ "responseBodyTests": [ { "key": "message", - "value": "Refund amount exceeds the payment amount" + "value": "The refund amount exceeds the amount captured" } ] } diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 1246c51d8eb..c1f59cb3635 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu powertranz prophetpay rapyd shift4 square stax stripe trustpay tsys volt wise worldline worldpay "$1") + connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe trustpay tsys volt wise worldline worldpay "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp diff --git a/scripts/decrypt_connector_auth.sh b/scripts/decrypt_connector_auth.sh deleted file mode 100755 index dc445f0afa6..00000000000 --- a/scripts/decrypt_connector_auth.sh +++ /dev/null @@ -1,10 +0,0 @@ -#! /usr/bin/env bash - -mkdir -p $HOME/target/test - - -# Decrypt the file -# --batch to prevent interactive command -# --yes to assume "yes" for questions -gpg --quiet --batch --yes --decrypt --passphrase="$CONNECTOR_AUTH_PASSPHRASE" \ ---output $HOME/target/test/connector_auth.toml .github/secrets/connector_auth.toml.gpg \ No newline at end of file