Skip to content

feat(router): add support for partial authorization #8833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 6, 2025

Conversation

sai-harsha-vardhan
Copy link
Contributor

@sai-harsha-vardhan sai-harsha-vardhan commented Aug 4, 2025

Type of Change

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

Description

add support for partial authorization in payments flow

Implementation

  1. Add enable_partial_authorization field to payments request and include it in the payment_intent table
  2. When enabled in the request, it is forwarded to downstream connectors as well
  3. In response handling amount_capturable and amount_captured populated in the connectors flow based on the amount approved in case of partial authorizations
  4. We retrieve the amount_capturable and amount_captured values populated from connector flows and use them to update our payment_intent and payment_attempt tables in post update trackers
  5. Based on the approved amount, mark intent status as partially_authorized_and_requires_capture and attempt status as partially_authorized in case of partial authorization
  6. Allow partially_authorized_and_requires_capture intent status to perform capture operation

API Changes

  1. Add enable_partial_authorization to payments request and response
  2. Add partially_authorized_and_requires_capture intent status
  3. Add partially_authorized attempt status

Database Changes

  1. Add enable_partial_authorization field to the payment_intent table

Additional Changes

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

Motivation and Context

Partial authorization helps minimize the number of declined transactions due to insufficient funds, increasing sales for merchants

How did you test it?

  1. Sanity Manual And Auto Capture flows (With amount_captured and amount_capturable being populated as expected)
2. Create a partial auth payment (with capture_method = manual) with `worldpayvantiv` connector, perform subsequent capture and refunds with partially approved amount (verify amount_capturable if populated correctly) a. Create a Payment Intent with `"enable_partial_authorization": "true"` CURL
curl --location '{{BASE_URL}}/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}' \
--data-raw '{
    "enable_partial_authorization": true,
    "amount": 100,
    "currency": "USD",
    "confirm": false,
    "capture_method": "manual",
    "customer_id": "testsai2",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payment request",
    "return_url": "https://google.com",
    "setup_future_usage": "off_session",
    "shipping_cost": 100
}'

Response

{
    "payment_id": "pay_WJVwMc4FnZanTbhI8uFT",
    "merchant_id": "merchant_1754308486",
    "status": "processing",
    "amount": 100,
    "net_amount": 200,
    "shipping_cost": 100,
    "amount_capturable": 160,
    "amount_received": null,
    "connector": "worldpayvantiv",
    "client_secret": "pay_WJVwMc4FnZanTbhI8uFT_secret_hBTPDrNhummuGv5IcsQo",
    "created": "2025-08-06T14:13:14.167Z",
    "currency": "USD",
    "customer_id": "testsai2",
    "customer": {
        "id": "testsai2",
        "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": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0004",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "511201",
            "card_extended_bin": null,
            "card_exp_month": "01",
            "card_exp_year": "2050",
            "card_holder_name": "John Smith",
            "payment_checks": {
                "avs_result": "00",
                "advanced_a_v_s_result": null,
                "authentication_result": null,
                "card_validation_result": null
            },
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "US",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "CA",
                "first_name": "John",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": null
        }
    },
    "payment_token": null,
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "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": {
        "customer_id": "testsai2",
        "created_at": 1754489594,
        "expires": 1754493194,
        "secret": "epk_98cdc5394c6a45aa99e469fbbb82afca"
    },
    "manual_retry_allowed": false,
    "connector_transaction_id": "83997375063155078",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_WJVwMc4FnZanTbhI8uFT_1",
    "payment_link": null,
    "profile_id": "pro_FW0rvO9wk1dBMZJoDRdb",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_pmcx4hlmJazlWDeZi4uo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-06T14:28:14.166Z",
    "fingerprint": null,
    "browser_info": {
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "125.0.0.1",
        "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,
        "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,
        "java_script_enabled": true
    },
    "payment_channel": null,
    "payment_method_id": null,
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-08-06T14:13:17.657Z",
    "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": true
}

b. Confirm the payment with following card_details and observe amount_capturable (should be less than total amount)
CURL

curl --location '{{BASE_URL}}/payments/{{PAYMENT_ID}}/confirm' \
--header 'api-key: {{API_KEY}}' \
--header 'Content-Type: application/json' \
--data '{
    "payment_method": "card",
    "payment_method_data": {
         "card": {
            "card_number": "5112010140000004",
            "card_exp_month": "01",
            "card_exp_year": "2050",
            "card_holder_name": "John Smith",
            "card_cvc": "349",
            "card_network": "Mastercard"
        },
        "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "CA",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        },
         "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"
    },
    "customer_acceptance": {
            "acceptance_type": "offline",
            "accepted_at": "1963-05-03T04:07:52.723Z",
            "online": {
                "ip_address": "125.0.0.1",
                "user_agent": "amet irure esse"
            }
        }
}'

Response

{
    "payment_id": "pay_IiDeX5MuG0m02mICx15E",
    "merchant_id": "merchant_1754308486",
    "status": "processing",
    "amount": 100,
    "net_amount": 200,
    "shipping_cost": 100,
    "amount_capturable": 160,
    "amount_received": null,
    "connector": "worldpayvantiv",
    "client_secret": "pay_IiDeX5MuG0m02mICx15E_secret_oj8EqUoys7RdnvitcV1w",
    "created": "2025-08-06T14:14:15.059Z",
    "currency": "USD",
    "customer_id": "testsai2",
    "customer": {
        "id": "testsai2",
        "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": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0004",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "511201",
            "card_extended_bin": null,
            "card_exp_month": "01",
            "card_exp_year": "2050",
            "card_holder_name": "John Smith",
            "payment_checks": {
                "avs_result": "00",
                "advanced_a_v_s_result": null,
                "authentication_result": null,
                "card_validation_result": null
            },
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "US",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "CA",
                "first_name": "John",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": null
        }
    },
    "payment_token": null,
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "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": "84085335970449288",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_IiDeX5MuG0m02mICx15E_1",
    "payment_link": null,
    "profile_id": "pro_FW0rvO9wk1dBMZJoDRdb",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_pmcx4hlmJazlWDeZi4uo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-06T14:29:15.059Z",
    "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": null,
    "payment_method_status": null,
    "updated": "2025-08-06T14:14:20.280Z",
    "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": true
}

c. force_sync the payment, and observe the intent status to be partially_authorized_and_requires_capture with partial amount set in amount_capturable
CURL

curl --location '{{BASE_URL}}/payments/{{PAYMENT_ID}}?force_sync=true' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}'

Response

{
    "payment_id": "pay_IiDeX5MuG0m02mICx15E",
    "merchant_id": "merchant_1754308486",
    "status": "partially_authorized_and_requires_capture",
    "amount": 100,
    "net_amount": 200,
    "shipping_cost": 100,
    "amount_capturable": 160,
    "amount_received": null,
    "connector": "worldpayvantiv",
    "client_secret": "pay_IiDeX5MuG0m02mICx15E_secret_oj8EqUoys7RdnvitcV1w",
    "created": "2025-08-06T14:14:15.059Z",
    "currency": "USD",
    "customer_id": "testsai2",
    "customer": {
        "id": "testsai2",
        "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": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0004",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "511201",
            "card_extended_bin": null,
            "card_exp_month": "01",
            "card_exp_year": "2050",
            "card_holder_name": "John Smith",
            "payment_checks": {
                "avs_result": "00",
                "advanced_a_v_s_result": null,
                "authentication_result": null,
                "card_validation_result": null
            },
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "US",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "CA",
                "first_name": "John",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": null
        }
    },
    "payment_token": null,
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "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": "84085335970449288",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_IiDeX5MuG0m02mICx15E_1",
    "payment_link": null,
    "profile_id": "pro_FW0rvO9wk1dBMZJoDRdb",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_pmcx4hlmJazlWDeZi4uo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-06T14:29:15.059Z",
    "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": "pm_Kdmb0Ife3AJSjAbVPNVl",
    "network_transaction_id": null,
    "payment_method_status": "inactive",
    "updated": "2025-08-06T14:14:47.492Z",
    "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": true
}

d. Perform capture for amount > amount_capturable which results in error

e. Perform capture for amount <= amount_capturable which results in successful capture and verify if amount_captured is populated as expected
CURL

curl --location '{{BASE_URL}}/payments/{{PAYMENT_ID}}/capture' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}' \
--data '{
  "amount_to_capture": 160,
  "statement_descriptor_name": "Joseph",
  "statement_descriptor_suffix": "JS"
}'

Response

{
    "payment_id": "pay_IiDeX5MuG0m02mICx15E",
    "merchant_id": "merchant_1754308486",
    "status": "processing",
    "amount": 100,
    "net_amount": 200,
    "shipping_cost": 100,
    "amount_capturable": 160,
    "amount_received": null,
    "connector": "worldpayvantiv",
    "client_secret": "pay_IiDeX5MuG0m02mICx15E_secret_oj8EqUoys7RdnvitcV1w",
    "created": "2025-08-06T14:14:15.059Z",
    "currency": "USD",
    "customer_id": "testsai2",
    "customer": {
        "id": "testsai2",
        "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": "on_session",
    "off_session": null,
    "capture_on": null,
    "capture_method": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "0004",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "511201",
            "card_extended_bin": null,
            "card_exp_month": "01",
            "card_exp_year": "2050",
            "card_holder_name": "John Smith",
            "payment_checks": {
                "avs_result": "00",
                "advanced_a_v_s_result": null,
                "authentication_result": null,
                "card_validation_result": null
            },
            "authentication_data": null
        },
        "billing": {
            "address": {
                "city": "San Fransico",
                "country": "US",
                "line1": "1467",
                "line2": "Harrison Street",
                "line3": "Harrison Street",
                "zip": "94122",
                "state": "CA",
                "first_name": "John",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": {
                "number": "8056594427",
                "country_code": "+91"
            },
            "email": null
        }
    },
    "payment_token": null,
    "shipping": null,
    "billing": null,
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": null,
    "statement_descriptor_suffix": null,
    "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": "84085335970460087",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_IiDeX5MuG0m02mICx15E_1",
    "payment_link": null,
    "profile_id": "pro_FW0rvO9wk1dBMZJoDRdb",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_pmcx4hlmJazlWDeZi4uo",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-08-06T14:29:15.059Z",
    "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": "pm_Kdmb0Ife3AJSjAbVPNVl",
    "network_transaction_id": null,
    "payment_method_status": null,
    "updated": "2025-08-06T14:15:16.689Z",
    "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": true
}

f. Perform refund for amount <= amount_captured

  1. Create a partial auth payment (with capture_method = automatic) with worldpayvantiv connector, perform subsequent refund with partially approved amount (verify amount_captured if populated correctly)

  2. Perform sanity CIT and MIT payment by sending enable_partial_authorization as true

  3. Perform sanity Partial Captures

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

@sai-harsha-vardhan sai-harsha-vardhan added this to the July 2025 Release milestone Aug 4, 2025
@sai-harsha-vardhan sai-harsha-vardhan self-assigned this Aug 4, 2025
@sai-harsha-vardhan sai-harsha-vardhan requested review from a team as code owners August 4, 2025 11:31
@sai-harsha-vardhan sai-harsha-vardhan added the A-connector-integration Area: Connector integration label Aug 4, 2025
@sai-harsha-vardhan sai-harsha-vardhan requested a review from a team as a code owner August 4, 2025 11:31
@sai-harsha-vardhan sai-harsha-vardhan added A-framework Area: Framework A-core Area: Core flows M-database-changes Metadata: This PR involves database schema changes M-api-contract-changes Metadata: This PR involves API contract changes labels Aug 4, 2025
Copy link

semanticdiff-com bot commented Aug 4, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/compatibility/stripe/webhooks.rs  81% smaller
  crates/router/src/compatibility/stripe/setup_intents/types.rs  79% smaller
  crates/router/src/core/payments/retry.rs  78% smaller
  crates/router/src/core/revenue_recovery/transformers.rs  78% smaller
  crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs  76% smaller
  crates/router/src/core/routing/helpers.rs  74% smaller
  crates/hyperswitch_domain_models/src/router_data.rs  68% smaller
  crates/router/src/compatibility/stripe/payment_intents/types.rs  64% smaller
  crates/hyperswitch_connectors/src/utils.rs  60% smaller
  crates/router/src/core/payments/operations/payment_response.rs  55% smaller
  crates/router/src/core/payments/helpers.rs  54% smaller
  crates/router/src/types.rs  46% smaller
  crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs  26% smaller
  crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs  26% smaller
  crates/router/src/types/transformers.rs  23% smaller
  crates/router/src/core/payments.rs  19% smaller
  crates/router/src/core/payments/operations/payment_attempt_record.rs  18% smaller
  crates/router/src/core/payments/operations/payment_capture_v2.rs  18% smaller
  crates/router/src/core/payments/operations/payment_confirm_intent.rs  18% smaller
  crates/router/src/core/payments/operations/payment_session_intent.rs  18% smaller
  crates/router/src/core/payments/operations/payment_update_intent.rs  18% smaller
  crates/router/src/core/payments/operations/proxy_payments_intent.rs  18% smaller
  crates/router/src/core/payments/payment_methods.rs  14% smaller
  crates/router/src/connector/utils.rs  13% smaller
  crates/common_enums/src/enums.rs  12% smaller
  crates/router/src/core/payments/operations/payment_get.rs  9% smaller
  api-reference/v1/openapi_spec_v1.json  0% smaller
  api-reference/v2/openapi_spec_v2.json  0% smaller
  crates/api_models/src/payments.rs  0% smaller
  crates/common_enums/src/transformers.rs  0% smaller
  crates/diesel_models/src/payment_attempt.rs  0% smaller
  crates/diesel_models/src/payment_intent.rs  0% smaller
  crates/diesel_models/src/schema.rs  0% smaller
  crates/diesel_models/src/schema_v2.rs  0% smaller
  crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs  0% smaller
  crates/hyperswitch_domain_models/src/payments.rs  0% smaller
  crates/hyperswitch_domain_models/src/payments/payment_attempt.rs  0% smaller
  crates/hyperswitch_domain_models/src/payments/payment_intent.rs  0% smaller
  crates/hyperswitch_domain_models/src/router_request_types.rs  0% smaller
  crates/hyperswitch_interfaces/src/conversion_impls.rs  0% smaller
  crates/router/src/core/authentication/transformers.rs  0% smaller
  crates/router/src/core/fraud_check/flows/checkout_flow.rs  0% smaller
  crates/router/src/core/fraud_check/flows/fulfillment_flow.rs  0% smaller
  crates/router/src/core/fraud_check/flows/record_return.rs  0% smaller
  crates/router/src/core/fraud_check/flows/sale_flow.rs  0% smaller
  crates/router/src/core/fraud_check/flows/transaction_flow.rs  0% smaller
  crates/router/src/core/mandate/utils.rs  0% smaller
  crates/router/src/core/payment_methods.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_update.rs  0% smaller
  crates/router/src/core/payments/transformers.rs  0% smaller
  crates/router/src/core/relay/utils.rs  0% smaller
  crates/router/src/core/unified_authentication_service/utils.rs Unsupported file format
  crates/router/src/core/utils.rs  0% smaller
  crates/router/src/core/webhooks/utils.rs  0% smaller
  crates/router/src/db/events.rs  0% smaller
  crates/router/src/types/api/verify_connector.rs  0% smaller
  crates/router/src/utils/user/sample_data.rs  0% smaller
  crates/router/tests/connectors/aci.rs  0% smaller
  crates/router/tests/connectors/utils.rs  0% smaller
  crates/router/tests/payments.rs  0% smaller
  crates/router/tests/payments2.rs  0% smaller
  migrations/2025-08-02-080018_add_partial_authorization_to_payment_intent/down.sql Unsupported file format
  migrations/2025-08-02-080018_add_partial_authorization_to_payment_intent/up.sql Unsupported file format
  migrations/2025-08-02-084127_add_partial_auth_enum_variants/down.sql Unsupported file format
  migrations/2025-08-02-084127_add_partial_auth_enum_variants/up.sql Unsupported file format

@hyperswitch-bot hyperswitch-bot bot removed the M-api-contract-changes Metadata: This PR involves API contract changes label Aug 4, 2025
@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Aug 4, 2025
@sai-harsha-vardhan sai-harsha-vardhan changed the title feat: add support for partial authorization feat(router): add support for partial authorization Aug 4, 2025
@@ -1677,6 +1680,8 @@ pub enum IntentStatus {
PartiallyCaptured,
/// The payment has been captured partially and the remaining amount is capturable
PartiallyCapturedAndCapturable,
/// The payment has been authorized for a partial amount and requires capture
PartiallyAuthorizedAndRequiresCapture,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this status needed? Wouldn't amount_capturable indicate if the payment was Fully authorized or Partially authorized?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There should be some explicit way of communicating the partial authorization to merchant, it's very similar to how we have PartialCharged (even though we give amount_captured value in the response)

@@ -65,6 +65,7 @@ pub struct PaymentIntent {
pub is_iframe_redirection_enabled: Option<bool>,
pub is_payment_id_from_merchant: Option<bool>,
pub payment_channel: Option<common_enums::PaymentChannel>,
pub enable_partial_authorization: Option<bool>,
Copy link
Contributor

Choose a reason for hiding this comment

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

We can make this a wrapper domain type. Like RequestIncrementalAuthorization

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In V1, we are following this convention. Can take this up in a separate PR if required

Copy link
Member

Choose a reason for hiding this comment

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

lets takes this in a separate PR, and moving forward don't repeat this irrespective of any versions

@@ -44,6 +44,7 @@ pub struct RouterData<Flow, Request, Response> {
pub connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
pub connector_wallets_details: Option<common_utils::pii::SecretSerdeValue>,
pub amount_captured: Option<i64>,
pub amount_capturable: Option<i64>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed if minor_amount_capturable is already there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added it in similar way to minor_amount_captured, currently minor_amount_captured is also not being utilized in the core. So, we are using amount_captured and amount_capturable only in payments_response_update_tracker. Introducing minor_amount_capturable allows the extensibility to possible refactor

Copy link
Contributor

Choose a reason for hiding this comment

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

Amount should always be represented with MinorAmount type instead of i64

@sai-harsha-vardhan sai-harsha-vardhan requested a review from a team as a code owner August 4, 2025 13:54
hrithikesh026
hrithikesh026 previously approved these changes Aug 4, 2025
Copy link
Contributor

@hrithikesh026 hrithikesh026 left a comment

Choose a reason for hiding this comment

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

Core changes LGTM

@@ -65,6 +65,7 @@ pub struct PaymentIntent {
pub is_iframe_redirection_enabled: Option<bool>,
pub is_payment_id_from_merchant: Option<bool>,
pub payment_channel: Option<common_enums::PaymentChannel>,
pub enable_partial_authorization: Option<bool>,
Copy link
Member

Choose a reason for hiding this comment

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

lets takes this in a separate PR, and moving forward don't repeat this irrespective of any versions

@@ -44,6 +44,7 @@ pub struct RouterData<Flow, Request, Response> {
pub connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
pub connector_wallets_details: Option<common_utils::pii::SecretSerdeValue>,
pub amount_captured: Option<i64>,
pub amount_capturable: Option<i64>,
Copy link
Member

Choose a reason for hiding this comment

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

Amount should be strictly MinorAmount only and not i64

== capturable_amount.map(MinorUnit::new)
{
enums::AttemptStatus::Authorized
} else if capturable_amount.is_some() {
Copy link
Member

Choose a reason for hiding this comment

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

Here we are implicitly assuming if the object present that means PartiallyAuthorized, instead if this also the explicit key which states this is partial_auth is enabled and the amount is less then PartiallyAuthorized, in other cases if amount is less and the object present is bug, so lets avoid implicit decision making instead do deterministic explicit conditions.

@@ -1936,7 +1939,16 @@ impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for
straight_through_algorithm: m_straight_through_algorithm,
error_code: m_error_code,
error_message: m_error_message,
amount_capturable: Some(authorized_amount),
amount_capturable: if payment_data
Copy link
Member

Choose a reason for hiding this comment

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

Instead of having None here, have a three branch enum which has strictly no value, then non changeable authorized_amount and changeable authorized_amount

@@ -248,6 +248,8 @@ pub struct Authorization {
#[serde(skip_serializing_if = "Option::is_none")]
pub original_network_transaction_id: Option<Secret<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_partial_auth: Option<bool>,
Copy link
Contributor

Choose a reason for hiding this comment

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

there's a feature called partial auth that already exists which is completely different than this. Make sure no one confuses this.

@likhinbopanna likhinbopanna added this pull request to the merge queue Aug 6, 2025
Merged via the queue into main with commit c354e62 Aug 6, 2025
21 of 26 checks passed
@likhinbopanna likhinbopanna deleted the add-partial-auth-support branch August 6, 2025 14:28
pixincreate added a commit that referenced this pull request Aug 7, 2025
…ordea-sepa

* 'main' of github.com:juspay/hyperswitch:
  fix(router): [worldpayvantiv] dispute validations and statuses (#8862)
  chore(version): 2025.08.07.0
  feat(connector): [WORLDPAYVANTIV] Populate Network Decline Error Code & Message (#8856)
  feat(router): add support for partial authorization (#8833)
  feat(gRPC): build gRPC client interface to initiate communication with recovery-decider service (#8178)
  fix(connector): [CYBERSOURCE] fix response field for netcetera authentication response (#8850)
  chore(events): making events nanosecond level precision (#8759)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-connector-integration Area: Connector integration A-core Area: Core flows A-framework Area: Framework M-api-contract-changes Metadata: This PR involves API contract changes M-database-changes Metadata: This PR involves database schema changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants