hyperswitch icon indicating copy to clipboard operation
hyperswitch copied to clipboard

feat(connector): [TSYS] Added Integrity Check Support for Authorize, PSync, Refund and RSync Flows

Open Jatan196 opened this issue 2 months ago • 4 comments

Type of Change

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

Issue No.

Fixes #9230

Description

In this PR, we have added integrity checks for Authorize, PSync, Capture, Refund, and RSync flows for the TSYS Connector.

What is an integrity check?
A scenario where there is a discrepancy between the amount sent in the request and the amount received from the connector, which is checked during response handling.

  • Added integrity object creation for all five flows in TSYS connector
  • Used helper functions from utils.rs to generate integrity objects
  • Added proper error handling with .change_context() for clearer error messages
  • Added info logs to track integrity object attachment
  • Used proper amount field for each flow: amount for PSync, minor_amount for Authorize, minor_amount_to_capture for Capture, and minor_refund_amount for Refunds

Additional Changes

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

Motivation and Context

This PR holds the payment in a non-terminal state in case there is any data discrepancy in the above flows, improving reliability and security. It also resolves a startup issue caused by missing configuration.

How did you test it?

Case 1: AUTHORIZE Flow

  • Used the TSYS connector's Authorize flow and tested with:
    • Hardcoded different amount in the mock to simulate connector discrepancy
    • Verified integrity check failure when amounts don't match
    • Confirmed successful integrity check when amounts match

Case 2: PAYMENT SYNC Flow

  • Used the TSYS connector's PSync flow and tested with:
    • Forced payment sync with mismatched amount values
    • Verified integrity check failure message and non-terminal status
    • Confirmed success case when amounts match

Case 3: CAPTURE Flow (Manual Capture)

  • Used the TSYS connector with manual capture method and tested with:
    • Created payment with capture_method: "manual"
    • Attempted capture with different amount than original authorization
    • Verified integrity check error for mismatched amounts
    • Confirmed successful capture with matching amounts

Case 4: REFUND Flow

  • Used the TSYS connector's refund flow and tested with:
    • Created payment and successful capture
    • Initiated refund with mismatched amounts
    • Verified integrity check failure and payment held in review status
    • Confirmed successful refund with matching amounts

Case 5: REFUND SYNC Flow

  • Used the TSYS connector's refund sync flow and tested with:
    • Created refund and forced sync
    • Simulated response with mismatched amounts
    • Verified integrity check failure
    • Confirmed successful handling with matching amounts

Checklist

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

Feature Description/Summary

Integrity check is a scenario where there is a discrepancy between the amount sent in the request and the amount received from the connector, which is checked during response handling.

Implementation Details

  • Added integrity object creation for all five flows in TSYS connector
  • Used helper functions from utils.rs to generate integrity objects
  • Added proper error handling with .change_context() for clearer error messages
  • Added info logs to track integrity object attachment
  • Used proper amount field for each flow: amount for PSync, minor_amount for Authorize, minor_amount_to_capture for Capture, and minor_refund_amount for Refunds

Test Cases

Authorize

Request :


{
    "amount": 2500,
    "currency": "AED",
    "connector": ["tsys"],
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 2500,
    "customer_id": "abcdef",
    "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://duck.com",
    "payment_method": "card",
    "payment_method_type": "credit",
    "payment_method_data": {
        "card": {
            "card_number": "4002400000000002",
            "card_exp_month": "12",
            "card_exp_year": "2028",
            "card_holder_name": "Max Mustermann",
            "card_cvc": "999"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "9123456789",
            "country_code": "+91"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "9123456789",
            "country_code": "+91"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "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": "109.71.40.0"
    }
}

Response:


{
    "error": {
        "type": "api",
        "message": "Integrity Check Failed! as data mismatched for amount expected 2500 but found 10",
        "code": "IE_00",
        "connector_transaction_id": "67102339"
    }
}

PSync

{
    "payment_id": "pay_sOm1KUFScqQtqoHU2cau",
    "merchant_id": "merchant_1760530422",
    "status": "conflicted",
    "amount": 2500,
    "net_amount": 2500,
    "shipping_cost": null,
    "amount_capturable": 0,
    "amount_received": 2500,
    "connector": "tsys",
    "client_secret": "pay_sOm1KUFScqQtqoHU2cau_secret_11lpvi2WNBZdZQRKvwBY",
    "created": "2025-10-15T12:15:42.959Z",
    "currency": "AED",
    "customer_id": "abcdef",
    "customer": {
        "id": "abcdef",
        "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": "0002",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "400240",
            "card_extended_bin": null,
            "card_exp_month": "12",
            "card_exp_year": "2028",
            "card_holder_name": "Max Mustermann",
            "payment_checks": null,
            "authentication_data": null
        },
        "billing": null
    },
    "payment_token": "token_gizQEEPj4a3Z0tR68dSF",
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": null,
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "9123456789",
            "country_code": "+91"
        },
        "email": null
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": null,
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "California",
            "first_name": "joseph",
            "last_name": "Doe",
            "origin_zip": null
        },
        "phone": {
            "number": "9123456789",
            "country_code": "+91"
        },
        "email": null
    },
    "order_details": null,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://duck.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": null,
    "cancellation_reason": null,
    "error_code": "IE",
    "error_message": "Integrity Check Failed! Value mismatched for fields amount expected 2500 but found 10",
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "credit",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": null,
    "manual_retry_allowed": null,
    "connector_transaction_id": "67101609",
    "frm_message": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "gateway_system": "direct"
    },
    "reference_id": null,
    "payment_link": null,
    "profile_id": "pro_WuHKzhKnO83bOhPlQXSL",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_camUR87QSGIErnkIEFY3",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2025-10-15T12:30:42.959Z",
    "fingerprint": null,
    "browser_info": {
        "os_type": null,
        "referer": null,
        "language": "nl-NL",
        "time_zone": 0,
        "ip_address": "109.71.40.0",
        "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-10-15T12:16:06.791Z",
    "split_payments": null,
    "frm_metadata": null,
    "extended_authorization_applied": null,
    "request_extended_authorization": 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,
    "enable_overcapture": null,
    "is_overcapture_enabled": null,
    "network_details": null,
    "is_stored_credential": null,
    "mit_category": null
}

Capture

{
    "error": {
        "type": "api",
        "message": "Integrity Check Failed! as data mismatched for capture_amount expected 2500 but found 10",
        "code": "IE_00",
        "connector_transaction_id": "67102683"
    }
}

Refund

Request:


{
    "payment_id": "{{payment_id}}",
    "amount": 2500,
    "reason": "Customer returned product",
    "refund_type": "instant"
}

Response:


{
    "refund_id": "ref_KzuLnsdCWbJA6buDsf5S",
    "payment_id": "pay_LdonKASrJYn4fyQXUvCO",
    "amount": 2500,
    "currency": "AED",
    "status": "review",
    "reason": "Customer returned product",
    "metadata": null,
    "error_message": "Integrity Check Failed! as data mismatched for fields refund_amount expected 2500 but found 10",
    "error_code": "IE",
    "unified_code": null,
    "unified_message": null,
    "created_at": "2025-10-15T12:27:09.057Z",
    "updated_at": "2025-10-15T12:27:10.230Z",
    "connector": "tsys",
    "profile_id": "pro_WuHKzhKnO83bOhPlQXSL",
    "merchant_connector_id": "mca_camUR87QSGIErnkIEFY3",
    "split_refunds": null,
    "issuer_error_code": null,
    "issuer_error_message": null
}

RSync

{
    "refund_id": "ref_KzuLnsdCWbJA6buDsf5S",
    "payment_id": "pay_LdonKASrJYn4fyQXUvCO",
    "amount": 2500,
    "currency": "AED",
    "status": "review",
    "reason": "Customer returned product",
    "metadata": null,
    "error_message": "Integrity Check Failed! as data mismatched for fields refund_amount expected 2500 but found 10",
    "error_code": "IE",
    "unified_code": null,
    "unified_message": null,
    "created_at": "2025-10-15T12:27:09.057Z",
    "updated_at": "2025-10-15T12:29:00.039Z",
    "connector": "tsys",
    "profile_id": "pro_WuHKzhKnO83bOhPlQXSL",
    "merchant_connector_id": "mca_camUR87QSGIErnkIEFY3",
    "split_refunds": null,
    "issuer_error_code": null,
    "issuer_error_message": null
}

Jatan196 avatar Oct 12 '25 08:10 Jatan196

Review changes with  SemanticDiff

Changed Files
File Status
  crates/hyperswitch_connectors/src/connectors/tsys.rs  9% smaller

semanticdiff-com[bot] avatar Oct 12 '25 08:10 semanticdiff-com[bot]

@Vani-1107 Hello ma'am, I have removed the logger from all flows. Pl let me know if there are any further changes required!!

Jatan196 avatar Oct 16 '25 19:10 Jatan196

@deepanshu-iiitu Hello Sir, Pl provide your review for this PR or any further changes to be made !!

Jatan196 avatar Oct 21 '25 05:10 Jatan196

@Vani-1107 @deepanshu-iiitu Hello there, Kindly provide me with the next review. I can't be assigned next issue until this one gets merged. Thats why I request to make it done asap, so that I can properly work on next issues.

Jatan196 avatar Oct 27 '25 10:10 Jatan196