Skip to content
New issue

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

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

Already on GitHub? # to your account

refactor(router): modify net_amount to be a struct in the domain model of payment_attempt and handle amount changes across all flows #6252

Merged
merged 17 commits into from
Oct 10, 2024

Conversation

sai-harsha-vardhan
Copy link
Contributor

@sai-harsha-vardhan sai-harsha-vardhan commented Oct 7, 2024

Type of Change

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

Description

Modify net_amount to be a struct in the domain model of payment_attempt and handle amount changes across all flows.

  1. Update the net_amount to be a struct and move order_amount, shipping_cost, order_tax_amount, surcharge_amount, and tax_on_surcharge into the NetAmount struct of payment_attempt
  2. Use net_amount.total_amount instead of payment_attempt.amount in all subsequent flows like captures, refunds, etc. Replace the usage of the intent amount with net_amount.total_amount wherever necessary.

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 Manually

  1. Payments Create with shipping_cost sent in the request, amount = 123, shipping_cost = 100. So, net_amount would be 223. The amount sent to the connector for authorize, capture and refunds should be able to be done against the net_amount of 223
    Create CURL
curl --location 'localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}' \
--data-raw '{
    "amount": 123,
    "currency": "USD",
    "confirm": false,
    "capture_method": "manual",
    "capture_on": "2022-09-10T10:11:12Z",
    "customer_id": "stripecustomer1",
    "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": "CA",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        },
         "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "shipping": {
        "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"
        }
    },
      "connector_metadata": {
        "noon": {
            "order_category": "pay"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {},
    "order_details": [
        {
            "product_name": "Apple iphone 15",
            "quantity":1,
            "amount": 0,
            "account_name": "transaction_processing"
        }
    ],
    "shipping_cost": 100
}'

Response

{
    "payment_id": "pay_gyjSd7RYumdhE82fTzM3",
    "merchant_id": "merchant_1728289111",
    "status": "requires_payment_method",
    "amount": 123,
    "net_amount": 223,
    "amount_capturable": 0,
    "amount_received": null,
    "connector": null,
    "client_secret": "pay_gyjSd7RYumdhE82fTzM3_secret_GXVzY7aJpNP0AW3U5phi",
    "created": "2024-10-07T09:37:31.867Z",
    "currency": "USD",
    "customer_id": "stripecustomer1",
    "customer": {
        "id": "stripecustomer1",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": null,
    "payment_method_data": null,
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "CA",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": 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"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": [
        {
            "brand": null,
            "amount": 0,
            "category": null,
            "quantity": 1,
            "product_id": null,
            "product_name": "Apple iphone 15",
            "product_type": null,
            "sub_category": null,
            "product_img_link": null,
            "product_tax_code": null,
            "requires_shipping": null
        }
    ],
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "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": {
        "customer_id": "stripecustomer1",
        "created_at": 1728293851,
        "expires": 1728297451,
        "secret": "epk_2e4863e0144e45f69020edc59ec3ebde"
    },
    "manual_retry_allowed": null,
    "connector_transaction_id": null,
    "frm_message": null,
    "metadata": {},
    "connector_metadata": {
        "apple_pay": null,
        "airwallex": null,
        "noon": {
            "order_category": "pay"
        }
    },
    "feature_metadata": null,
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_0W1FrymADX9vzJ4kAifW",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": null,
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-10-07T09:52:31.867Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2024-10-07T09:37:31.906Z",
    "charges": null,
    "frm_metadata": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null
}

Confirm CURL

curl --location '{{BASE_URL}}/payments/{{PAYMENT_ID}}/confirm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}' \
--data '{
    "payment_method_data": {
        "card": {
            "card_number": "4111111111111111",
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": "John Doe",
            "card_cvc": "737"
        }
    },
    "payment_method": "card",
    "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"
    }
}'

Response

{
    "payment_id": "pay_gyjSd7RYumdhE82fTzM3",
    "merchant_id": "merchant_1728289111",
    "status": "requires_capture",
    "amount": 123,
    "net_amount": 223,
    "amount_capturable": 223,
    "amount_received": null,
    "connector": "checkout",
    "client_secret": "pay_gyjSd7RYumdhE82fTzM3_secret_GXVzY7aJpNP0AW3U5phi",
    "created": "2024-10-07T09:37:31.867Z",
    "currency": "USD",
    "customer_id": "stripecustomer1",
    "customer": {
        "id": "stripecustomer1",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "1111",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "411111",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": null,
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "CA",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": 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"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": [
        {
            "brand": null,
            "amount": 0,
            "category": null,
            "quantity": 1,
            "product_id": null,
            "product_name": "Apple iphone 15",
            "product_type": null,
            "sub_category": null,
            "product_img_link": null,
            "product_tax_code": null,
            "requires_shipping": null
        }
    ],
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "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": "pay_u6bxdvaern5edlrbpoyqf4ci2e",
    "frm_message": null,
    "metadata": {},
    "connector_metadata": {
        "apple_pay": null,
        "airwallex": null,
        "noon": {
            "order_category": "pay"
        }
    },
    "feature_metadata": null,
    "reference_id": "pay_gyjSd7RYumdhE82fTzM3_1",
    "payment_link": null,
    "profile_id": "pro_0W1FrymADX9vzJ4kAifW",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_xun5DuZr4E1oAMIRghlT",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-10-07T09:52:31.867Z",
    "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_method_id": null,
    "payment_method_status": null,
    "updated": "2024-10-07T09:37:38.501Z",
    "charges": null,
    "frm_metadata": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null
}

Amount sent to connector
image

Capture the payment for net_amount 223
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": 223,
  "statement_descriptor_name": "Joseph",
  "statement_descriptor_suffix": "JS"
}'

Response

{
    "payment_id": "pay_gyjSd7RYumdhE82fTzM3",
    "merchant_id": "merchant_1728289111",
    "status": "succeeded",
    "amount": 123,
    "net_amount": 223,
    "amount_capturable": 0,
    "amount_received": 223,
    "connector": "checkout",
    "client_secret": "pay_gyjSd7RYumdhE82fTzM3_secret_GXVzY7aJpNP0AW3U5phi",
    "created": "2024-10-07T09:37:31.867Z",
    "currency": "USD",
    "customer_id": "stripecustomer1",
    "customer": {
        "id": "stripecustomer1",
        "name": "John Doe",
        "email": "guest@example.com",
        "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": "manual",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "last4": "1111",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "411111",
            "card_extended_bin": null,
            "card_exp_month": "03",
            "card_exp_year": "2030",
            "card_holder_name": null,
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "CA",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": 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"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": [
        {
            "brand": null,
            "amount": 0,
            "category": null,
            "quantity": 1,
            "product_id": null,
            "product_name": "Apple iphone 15",
            "product_type": null,
            "sub_category": null,
            "product_img_link": null,
            "product_tax_code": null,
            "requires_shipping": null
        }
    ],
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://google.com/",
    "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": "pay_u6bxdvaern5edlrbpoyqf4ci2e",
    "frm_message": null,
    "metadata": {},
    "connector_metadata": {
        "apple_pay": null,
        "airwallex": null,
        "noon": {
            "order_category": "pay"
        }
    },
    "feature_metadata": null,
    "reference_id": "pay_gyjSd7RYumdhE82fTzM3_1",
    "payment_link": null,
    "profile_id": "pro_0W1FrymADX9vzJ4kAifW",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_xun5DuZr4E1oAMIRghlT",
    "incremental_authorization_allowed": false,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-10-07T09:52:31.867Z",
    "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_method_id": null,
    "payment_method_status": null,
    "updated": "2024-10-07T09:42:05.385Z",
    "charges": null,
    "frm_metadata": null,
    "merchant_order_reference_id": null,
    "order_tax_amount": null,
    "connector_mandate_id": null
}

image

Refund for net_amount of 223
CURL

curl --location '{{BASE_URL}}/refunds' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: {{API_KEY}}' \
--data '{
    "payment_id": "{{PAYMENT_ID}}",
    "refund_id": "ref_1728294430",
    "amount": 223,
    "reason": "RETURN",
    "refund_type": "instant",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'

Response

{
    "refund_id": "ref_1728294426",
    "payment_id": "pay_gyjSd7RYumdhE82fTzM3",
    "amount": 223,
    "currency": "USD",
    "status": "succeeded",
    "reason": "RETURN",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    },
    "error_message": null,
    "error_code": null,
    "created_at": "2024-10-07T09:47:06.120Z",
    "updated_at": "2024-10-07T09:47:06.917Z",
    "connector": "checkout",
    "profile_id": "pro_0W1FrymADX9vzJ4kAifW",
    "merchant_connector_id": "mca_xun5DuZr4E1oAMIRghlT",
    "charges": null
}

image

  1. Sanity Surcharge flow with net_amount subsequent flows like captures & refunds (partial-refunds)

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 A-core Area: Core flows C-refactor Category: Refactor labels Oct 7, 2024
@sai-harsha-vardhan sai-harsha-vardhan self-assigned this Oct 7, 2024
@sai-harsha-vardhan sai-harsha-vardhan requested review from a team as code owners October 7, 2024 12:57
Copy link

semanticdiff-com bot commented Oct 7, 2024

Review changes with SemanticDiff.

Analyzed 36 of 36 files.

Overall, the semantic diff is 11% smaller than the GitHub diff.

File Information
Filename Status
✔️ crates/storage_impl/src/payments/payment_attempt.rs 0.9% smaller
✔️ crates/storage_impl/src/mock_db/payment_attempt.rs Analyzed
✔️ crates/router/src/types.rs 28.31% smaller
✔️ crates/router/src/types/transformers.rs 72.22% smaller
✔️ crates/router/src/types/storage/payment_attempt.rs 38.49% smaller
✔️ crates/router/src/services/kafka/payment_attempt.rs 42.97% smaller
✔️ crates/router/src/services/kafka/payment_attempt_event.rs 42.97% smaller
✔️ crates/router/src/core/payment_link.rs 51.01% smaller
✔️ crates/router/src/core/payments.rs 26.27% smaller
✔️ crates/router/src/core/refunds.rs Analyzed
✔️ crates/router/src/core/payments/helpers.rs 5.0% smaller
✔️ crates/router/src/core/payments/retry.rs 16.45% smaller
✔️ crates/router/src/core/payments/routing.rs Analyzed
✔️ crates/router/src/core/payments/transformers.rs 30.08% smaller
✔️ crates/router/src/core/payments/types.rs Analyzed
✔️ crates/router/src/core/payments/operations/payment_complete_authorize.rs Analyzed
✔️ crates/router/src/core/payments/operations/payment_confirm.rs 53.92% smaller
✔️ crates/router/src/core/payments/operations/payment_create.rs 17.2% smaller
✔️ crates/router/src/core/payments/operations/payment_response.rs 86.94% smaller
✔️ crates/router/src/core/payments/operations/payment_update.rs 1.84% smaller
✔️ crates/router/src/core/payments/operations/payments_incremental_authorization.rs 55.58% smaller
✔️ crates/router/src/core/payment_methods/cards.rs 39.09% smaller
✔️ crates/router/src/core/payment_methods/surcharge_decision_configs.rs 1.46% smaller
✔️ crates/router/src/core/fraud_check/flows/checkout_flow.rs 81.18% smaller
✔️ crates/router/src/core/fraud_check/flows/fulfillment_flow.rs 77.04% smaller
✔️ crates/router/src/core/fraud_check/flows/record_return.rs 81.18% smaller
✔️ crates/router/src/core/fraud_check/flows/sale_flow.rs 81.18% smaller
✔️ crates/router/src/core/fraud_check/flows/transaction_flow.rs 81.18% smaller
✔️ crates/hyperswitch_domain_models/src/router_request_types.rs 5.58% smaller
✔️ crates/hyperswitch_domain_models/src/payments/payment_attempt.rs 1.62% smaller
✔️ crates/diesel_models/src/payment_attempt.rs Analyzed
✔️ crates/diesel_models/src/query/payment_attempt.rs Analyzed
✔️ crates/api_models/src/payment_methods.rs Analyzed
✔️ crates/api_models/src/payments.rs Analyzed
✔️ api-reference-v2/openapi_spec.json 25.0% smaller
✔️ api-reference/openapi_spec.json 25.0% smaller


pub fn set_surcharge_details(
&mut self,
surcharge_details: Option<crate::router_request_types::SurchargeDetails>,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
surcharge_details: Option<crate::router_request_types::SurchargeDetails>,
surcharge_details: Option<router_request_types::SurchargeDetails>,

@@ -303,9 +290,10 @@ impl PaymentAttemptNew {
/// returns amount + surcharge_amount + tax_amount (surcharge) + shipping_cost + order_tax_amount
pub fn calculate_net_amount(&self) -> MinorUnit {
Copy link
Member

Choose a reason for hiding this comment

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

Why this function is required when we have get_total_amount from NetAmount

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This impl is on diesel models of PaymentAttemptNew, in which net_amount is still a MinorUnit

surcharge: common_utils::types::Surcharge::Fixed(
request_surcharge_details.surcharge_amount,
),
tax_on_surcharge: None,
surcharge_amount,
tax_on_surcharge_amount,
final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount,
final_amount: payment_attempt.net_amount.get_order_amount()
Copy link
Member

Choose a reason for hiding this comment

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

Why we are not considering get_total_amount here? Reason for again doing calculation here.

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 trying to calculate surcharge_details from the RequestSurchargeDetails, net_amount is not computed yet. That's why we are getting surcharge details from RequestSurchargeDetails and calculating SurchargeDetails

.net_amount
.get_tax_on_surcharge()
.unwrap_or_default();
let final_amount = payment_attempt.net_amount.get_order_amount()
Copy link
Member

Choose a reason for hiding this comment

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

Why we are calculation net_amount instead of using get_total_amount function?

.or(payment_attempt
.and_then(|payment_attempt| payment_attempt.net_amount.get_shipping_cost()))
.unwrap_or_default();
let total_capturable_amount = original_amount
Copy link
Member

Choose a reason for hiding this comment

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

Why we are calculating this instead of using get_total_amount?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is being called in validate_request flow also, at that point payment_attempt is not present yet. So, we have to take details from request and calculate the amount

@@ -106,6 +100,12 @@ impl<F: Send + Clone>
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

if payment_attempt.get_total_amount() > request.amount {
Copy link
Member

Choose a reason for hiding this comment

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

This can't be true with get_total_amount, because request.amount is a order_amount which can be lower than the get_total_amount. Or In incremental_authotization they can pass total amount in request?

If genuine cases also this can throw errors.

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 incremental_authotization, request.amount is total_amount which is on top of net_amount (not order_amount here)

Copy link
Member

Choose a reason for hiding this comment

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

Add comments explain the case

@@ -232,7 +232,7 @@ pub fn make_dsl_input(
};

let payment_input = dsl_inputs::PaymentInput {
amount: payments_dsl_input.payment_intent.amount,
amount: payments_dsl_input.payment_attempt.get_total_amount(),
Copy link
Member

Choose a reason for hiding this comment

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

@Aprabhat19 Is routing need total_amount or just order_amount? In other places dsl_inputs do we need total amount?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally we should be doing on net_amount right? @Aprabhat19

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes we would do it on total_amount, cause he could have a rule saying amount>1000, route it to connector_b. So total_amount would be a more accurate field for making such decisions

@@ -1340,8 +1331,11 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
client_source,
client_version,
customer_acceptance: payment_data.payment_attempt.customer_acceptance,
shipping_cost,
order_tax_amount,
shipping_cost: payment_data.payment_intent.shipping_cost,
Copy link
Member

Choose a reason for hiding this comment

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

we need not have these fields in the flattened format in update right? can we use the amount struct in update as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on the flow, we would need to restrict the updating of fields. Let's say in payments update flow, we don't want to update surcharge and tax amount.

@@ -694,6 +694,10 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest, PaymentData<F>> for Paymen
}),
payment_method_type: None,
});
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.

we should not update payment attempt when calculating the tax, since this operation is not dependent on payment attempt. @swangi-kumari can you please confirm this once?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes ideally it shouldn't depend on payment attempt but in v1, we are storing tax calculation details in net_amount of payment_attempt. In v2, we can have different way to handle this

Copy link
Member

Choose a reason for hiding this comment

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

There is separate field storing this data in intent itself, @swangi-kumari can you confirm this once?

Copy link
Contributor

Choose a reason for hiding this comment

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

We store tax_details in payment_intent

pub struct PaymentIntent {
    pub tax_details: Option<TaxDetails>,
}
pub struct TaxDetails {
    pub default: Option<DefaultTax>,
    pub payment_method_type: Option<PaymentMethodTypeTax>,
}

Copy link
Contributor Author

@sai-harsha-vardhan sai-harsha-vardhan Oct 9, 2024

Choose a reason for hiding this comment

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

Yes, we store tax_details in payment_intent, but we would need to update payment_attempt.net_amount with order_tax_amount too so that all the subsequent flows would use the updated net_amount which includes order_tax_amount

Copy link
Member

Choose a reason for hiding this comment

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

After confirm only we need to compute total_amount using intent, for create the related information will be stored on intent and the computation also on intent.

Comment on lines 1177 to 1180
MinorUnit::from(amount),
request.shipping_cost,
None,
surcharge_amount,
Copy link
Member

Choose a reason for hiding this comment

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

since all the fields here are MinorUnit, there might be chances where one amount is passed as another amount. Can we instead have this as a from function from the request instead of the new function, we can preserve the context of fields in that case when converting.

@@ -694,6 +694,10 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest, PaymentData<F>> for Paymen
}),
payment_method_type: None,
});
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.

There is separate field storing this data in intent itself, @swangi-kumari can you confirm this once?

@@ -106,6 +100,12 @@ impl<F: Send + Clone>
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

if payment_attempt.get_total_amount() > request.amount {
Copy link
Member

Choose a reason for hiding this comment

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

Add comments explain the case

@@ -212,8 +212,9 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe
.tax_on_surcharge_amount
.get_amount_as_i64(),
)?;
let display_final_amount = currency
.to_currency_base_unit_asf64(surcharge_details.final_amount.get_amount_as_i64())?;
let display_final_amount = currency.to_currency_base_unit_asf64(
Copy link
Member

Choose a reason for hiding this comment

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

@hrithikesh026 Is this final_amount is total_amount including all amount related calculation or only order_amount + surcharge?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently display_final_amount is not being used, removing it

@@ -528,6 +526,10 @@ impl SurchargeDetails {
pub fn get_total_surcharge_amount(&self) -> MinorUnit {
self.surcharge_amount + self.tax_on_surcharge_amount
}

pub fn get_final_amount(&self) -> MinorUnit {
Copy link
Member

Choose a reason for hiding this comment

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

Do we really needed this get_final_amount with just order_amount?

@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label Oct 9, 2024
@@ -694,6 +694,10 @@ impl<F: Clone + Send> Domain<F, api::PaymentsRequest, PaymentData<F>> for Paymen
}),
payment_method_type: None,
});
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.

After confirm only we need to compute total_amount using intent, for create the related information will be stored on intent and the computation also on intent.

Copy link
Member

@jarnura jarnura left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

@jarnura jarnura left a comment

Choose a reason for hiding this comment

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

@Gnanasundari24 This is a critical change, please test all payment, refund and dispute flows.

@bernard-eugine bernard-eugine merged commit 5930089 into main Oct 10, 2024
16 of 17 checks passed
@bernard-eugine bernard-eugine deleted the refactor-net-amount branch October 10, 2024 12:57
sai-harsha-vardhan added a commit that referenced this pull request Oct 15, 2024
…del of payment_attempt and handle amount changes across all flows (#6252)

Co-authored-by: swangi-kumari <swangi.12015941@lpu.in>
Co-authored-by: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-core Area: Core flows C-refactor Category: Refactor M-api-contract-changes Metadata: This PR involves API contract changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants