Skip to content

Conversation

Sakilmostak
Copy link
Contributor

@Sakilmostak Sakilmostak commented Sep 8, 2025

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Currently manual retry is enabled through request.
We need to make it profile configurable such that the merchant/SDK doesn't need to send it for each request

  • is_manual_retry is enabled through profile with this pr
  • if it is enabled, it lets merchant do multiple confirm with the same client_secret/payment_id
  • This helps in manually retrying the payment with other payment method if the current one fails
  • the behaviour in the core is changed, the condition is not based on profile config rather than request
  • the response is populated as well based on profile config

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

Tested through Postman:
Create an MCA(Cybersource):
Case 1: (without manual retry enabled)

  • Create an intent:
curl --location '{{baseUrl}}/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data-raw '{
    "amount": 10,
    "currency": "EUR",
    "confirm": false,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "cus_r4hlpxACcjdVr64ypkwQ",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://hyperswitch.io",
    "billing": {
        "address": {
            "city": "test",
            "country": "AT",
            "line1": "here is some \n there is some \n none is some? \n ",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Varaždin County",
            "first_name": "S@k!l",
            "last_name": "M*st@k"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "AT",
            "first_name": "PiX"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
    ,
    "setup_future_usage": "on_session"
}'
  • Create an confirm (failed)
curl --location '{{baseUrl}}/payments/pay_WCYhYBAJAarSdk2xosGs/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data-raw '{
    "payment_method": "card",
    "retry_action": "manual_retry",
    "payment_method_data": {
        "billing": {
            "address": {
                "city": "sakilmostak",
                "country": "US",
                "line1": "here",
                "line2": "there",
                "line3": "anywhere",
                "zip": "560090",
                "state": "Washington",
                "first_name": "One",
                "last_name": "Two"
            },
            "phone": {
                "number": "1234567890",
                "country_code": "+1"
            },
            "email": "[email protected]"
        },
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "02",
            "card_exp_year": "2026",
            "card_holder_name": "Sakil Mostak",
            "card_cvc": "999"
            
        }
    },
    "billing": {
        "address": {
            "city": "Mostak",
            "country": "GB",
            "line1": "here is some \n there is some \n none is some? \n ",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Varaždin County",
            "first_name": "Sakil",
            "last_name": "Mostak"
        }
    },
    "client_secret": "pay_WCYhYBAJAarSdk2xosGs_secret_BKtUnpRi4V90A5pqlvb6",
    "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"
    }
}'
  • Try to confirm again, it should through the below error
{
    "error": {
        "type": "invalid_request",
        "message": "You cannot confirm this payment because it has status failed, you can enable `manual_retry` in profile to try this payment again",
        "code": "IR_16"
    }
}

Case 2: (with manual retry enabeld)

  • Enable manual retry in profile
curl --location '{{baseUrl}}/account/{{merchant_id}}/business_profile/{{profile_id}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data '{
    "is_manual_retry_enabled": true
}'
  • Create an intent:
curl --location '{{baseUrl}}/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data-raw '{
    "amount": 10,
    "currency": "EUR",
    "confirm": false,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "cus_r4hlpxACcjdVr64ypkwQ",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://hyperswitch.io",
    "billing": {
        "address": {
            "city": "test",
            "country": "AT",
            "line1": "here is some \n there is some \n none is some? \n ",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Varaždin County",
            "first_name": "S@k!l",
            "last_name": "M*st@k"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "AT",
            "first_name": "PiX"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
    ,
    "setup_future_usage": "on_session"
}'
  • Create an confirm (failed)
curl --location '{{baseUrl}}/payments/pay_WCYhYBAJAarSdk2xosGs/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data-raw '{
    "payment_method": "card",
    "retry_action": "manual_retry",
    "payment_method_data": {
        "billing": {
            "address": {
                "city": "sakilmostak",
                "country": "US",
                "line1": "here",
                "line2": "there",
                "line3": "anywhere",
                "zip": "560090",
                "state": "Washington",
                "first_name": "One",
                "last_name": "Two"
            },
            "phone": {
                "number": "1234567890",
                "country_code": "+1"
            },
            "email": "[email protected]"
        },
        "card": {
            "card_number": "4242424242424242",
            "card_exp_month": "02",
            "card_exp_year": "2026",
            "card_holder_name": "Sakil Mostak",
            "card_cvc": "999"
            
        }
    },
    "billing": {
        "address": {
            "city": "Mostak",
            "country": "GB",
            "line1": "here is some \n there is some \n none is some? \n ",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Varaždin County",
            "first_name": "Sakil",
            "last_name": "Mostak"
        }
    },
    "client_secret": "pay_WCYhYBAJAarSdk2xosGs_secret_BKtUnpRi4V90A5pqlvb6",
    "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"
    }
}'
  • Try to confirm again with correct request, it should be able to make the payment (success) like below
{
    "payment_id": "pay_yZ9S88KzkI8uWZ20No6M",
    "merchant_id": "merchant_1757326993",
    "status": "succeeded",
    "amount": 10,
    "net_amount": 10,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 10,
    "connector": "cybersource",
    "client_secret": "pay_yZ9S88KzkI8uWZ20No6M_secret_qkjjAiTzixC5O58X1K3A",
    "created": "2025-09-08T10:24:10.474Z",
    "currency": "EUR",
    "customer_id": "cus_r4hlpxACcjdVr64ypkwQ",
    "customer": {
        "id": "cus_r4hlpxACcjdVr64ypkwQ",
        "name": "John Doe",
        "email": "[email protected]",
        "phone": "999999999",
        "phone_country_code": "+65"
    },
    "description": "Its my first payment request",
    "refunds": null,
    "disputes": null,
    "mandate_id": null,
    "mandate_data": null,
    "setup_future_usage": null,
    "off_session": null,
    "capture_on": null,
    "capture_method": "automatic",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "4242",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "424242",
            "card_extended_bin": null,
            "card_exp_month": "02",
            "card_exp_year": "2026",
            "card_holder_name": "Sakil Mostak",
            "payment_checks": {
                "avs_response": {
                    "code": "X",
                    "codeRaw": "I1"
                },
                "card_verification": null
            },
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "sakilmostak",
                "country": "US",
                "line1": "here",
                "line2": "there",
                "line3": "anywhere",
                "zip": "560090",
                "state": "Washington",
                "first_name": "One",
                "last_name": "Two",
                "origin_zip": null
            },
            "phone": {
                "number": "1234567890",
                "country_code": "+1"
            },
            "email": "[email protected]"
        }
    },
    "payment_token": "token_85KAtvBNQqHs4BoAdG0y",
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "AT",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "PiX",
            "last_name": null,
            "origin_zip": null
        },
        "phone": null,
        "email": null
    },
    "billing": {
        "address": {
            "city": "Mostak",
            "country": "GB",
            "line1": "here is some \n there is some \n none is some? \n ",
            "line2": "there",
            "line3": "anywhere",
            "zip": "560095",
            "state": "Varaždin County",
            "first_name": "Sakil",
            "last_name": "Mostak",
            "origin_zip": null
        },
        "phone": null,
        "email": null
    },
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://hyperswitch.io/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": null,
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": false,
    "connector_transaction_id": "7573271056056775504805",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": "pay_yZ9S88KzkI8uWZ20No6M_3",
    "payment_link": null,
    "profile_id": "pro_f5v9oKg0pFd4YK6UA03Y",
    "surcharge_details": null,
    "attempt_count": 3,
    "merchant_decision": null,
    "merchant_connector_id": "mca_DfPtPa3pBRQmbymilR38",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-09-08T10:39:10.474Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": null,
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "125.0.0.1",
        "os_version": null,
        "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",
        "color_depth": 24,
        "device_model": null,
        "java_enabled": true,
        "screen_width": 1536,
        "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "screen_height": 723,
        "accept_language": "en",
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": "123456789619999",
    "payment_method_status": null,
    "updated": "2025-09-08T10:25:05.916Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "capture_before": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null,
    "card_discovery": "manual",
    "force_3ds_challenge": false,
    "force_3ds_challenge_trigger": false,
    "issuer_error_code": null,
    "issuer_error_message": null,
    "is_iframe_redirection_enabled": null,
    "whole_connector_response": null,
    "enable_partial_authorization": null
}

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

@Sakilmostak Sakilmostak self-assigned this Sep 8, 2025
@Sakilmostak Sakilmostak requested a review from a team as a code owner September 8, 2025 09:51
@Sakilmostak Sakilmostak added A-core Area: Core flows C-refactor Category: Refactor A-payments Area: payments labels Sep 8, 2025
Copy link

semanticdiff-com bot commented Sep 8, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/core/payments/transformers.rs  46% smaller
  crates/router/src/core/payments/helpers.rs  43% smaller
  crates/api_models/src/enums.rs  0% smaller
  crates/router/src/core/payments.rs  0% smaller
  crates/router/src/core/payments/operations/payment_approve.rs  0% smaller
  crates/router/src/core/payments/operations/payment_cancel.rs  0% smaller
  crates/router/src/core/payments/operations/payment_cancel_post_capture.rs  0% smaller
  crates/router/src/core/payments/operations/payment_capture.rs  0% smaller
  crates/router/src/core/payments/operations/payment_complete_authorize.rs  0% smaller
  crates/router/src/core/payments/operations/payment_confirm.rs  0% smaller
  crates/router/src/core/payments/operations/payment_create.rs  0% smaller
  crates/router/src/core/payments/operations/payment_post_session_tokens.rs  0% smaller
  crates/router/src/core/payments/operations/payment_reject.rs  0% smaller
  crates/router/src/core/payments/operations/payment_session.rs  0% smaller
  crates/router/src/core/payments/operations/payment_start.rs  0% smaller
  crates/router/src/core/payments/operations/payment_status.rs  0% smaller
  crates/router/src/core/payments/operations/payment_update.rs  0% smaller
  crates/router/src/core/payments/operations/payment_update_metadata.rs  0% smaller
  crates/router/src/core/payments/operations/payments_incremental_authorization.rs  0% smaller
  crates/router/src/core/payments/operations/tax_calculation.rs  0% smaller

@hyperswitch-bot hyperswitch-bot bot requested a review from a team as a code owner September 8, 2025 09:58
@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Sep 8, 2025
Base automatically changed from make_manual_retry_profile_configurable to main September 8, 2025 10:44
@hyperswitch-bot hyperswitch-bot bot added the M-database-changes Metadata: This PR involves database schema changes label Sep 8, 2025
@hyperswitch-bot hyperswitch-bot bot removed M-database-changes Metadata: This PR involves database schema changes M-api-contract-changes Metadata: This PR involves API contract changes labels Sep 8, 2025
Comment on lines +4422 to +4427
is_manual_retry_enabled: Option<bool>,
action: &str,
) -> RouterResult<AttemptType> {
match payment_intent.status {
enums::IntentStatus::Failed => {
if matches!(
request.retry_action,
Some(api_models::enums::RetryAction::ManualRetry)
) {
if matches!(is_manual_retry_enabled, Some(true)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we removing the request support?
like we wont rely at all on request?

what happens to the current merchant who is sending manual retry config in request? will we inform them to update profile.

ig we need to check both for now imo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are deprecating the request parameter for manual retry. No merchant is actively using this flow. This is to simplify the solution since we don't want SDK to have separate approach for non-redirection and redirection case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we are removing the request parameter.
seems like those changes are not in this pr. i mean we need to update the api ref that we are deprecating this field.
Will there be a seperate pr?

Copy link
Contributor Author

@Sakilmostak Sakilmostak Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the reference to deprecated

@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Sep 11, 2025
Merged via the queue into main with commit 59870a9 Sep 11, 2025
21 of 25 checks passed
@Gnanasundari24 Gnanasundari24 deleted the change_manual_retry_behaviour branch September 11, 2025 14:32
pixincreate added a commit that referenced this pull request Sep 11, 2025
…ee-ds

* 'main' of github.com:juspay/hyperswitch:
  feat(webhooks): Provide outgoing webhook support for revenue recovery (#9294)
  feat(connector): Add Peachpayments Template Code (#9363)
  feat(connector): [Paysafe] Implement card 3ds flow (#9305)
  feat(router): Add Connector changes for 3ds (v2) (#9117)
  feat(connector): [ADYEN] Add support to ideal Mandate Webhook (#9347)
  refactor(core): accept manual retry from profile  (#9302)
  fix(nuvei): nuvei 3ds fix + psync fix (#9279)
  fix(connector): [checkout] Add US Support for Apple Pay and Google Pay + Enhanced Checkout Response Data (#9356)
  fix(router): adding connector_customer_id for external vault proxy (#9263)
  feat(core): Add first_name and last_name as Secret<String> Types.  (#9326)
  feat(injector): injector request formation changes (#9306)
  fix(revenue-recovery): Update Redis TTL for customer locks after token selection (#9282)
  chore(version): 2025.09.11.0
  refactor(connector): [Paysafe] fix wasm (#9349)
  refactor(connector): rename RevenueRecoveryRecordBack as InvoiceRecordBack (#9321)
  feat(connector): [checkout] add support for MOTO payments (#9327)
  feat(connector): enhance ACI connector with comprehensive 3DS support - DRAFT (#8986)
  feat(core): [Retry] MIT Retries (#8628)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows A-payments Area: payments C-refactor Category: Refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants