Skip to content

Conversation

@kanikac199
Copy link
Contributor

@kanikac199 kanikac199 commented Jan 21, 2026

Description

Added UPI Credit Card (UPI_CC) and UPI Credit Line (UPI_CL) detection from PhonePe connector responses, and sending the upi_source back in payment sync response in additional_payment_method_connector_response

Motivation and Context

Additional Changes

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

How did you test it?

Hyperswitch v2:
Create + confirm - Request

curl --location 'http://localhost:8080/v2/payments' \
--header 'Content-Type: application/json' \
--header 'x-profile-id: -' \
--header 'X-Merchant-Id: -' \
--header 'x-tenant-id: public' \
--header 'Authorization: api-key=-' \
--data-raw '{
    "amount_details": {
        "order_amount": 1000,
        "currency": "INR"
    },
    "merchant_connector_details": {
        "connector_name": "phonepe",
        "merchant_connector_creds": {
        "auth_type": "SignatureKey",
        "api_key": "IRCTCUAT",
        "key1": "-",
        "api_secret": "-"
        }
    },
    "webhook_url": "/XXXXXXXXXXXXXXXXXXXXXXXX",
    "merchant_reference_id": "razorpayirctc1768980371",
    
    "payment_method_data": {
      "upi": {
        "upi_intent": {
          
          "upi_source": "UPI_CC"
        }
      },
      "billing": {
        "address": {
          "city": "San Francisco",
          "country": "IN",
          "line1": "123 Main St",
          "line2": "Apt 4B",
          "line3": "Building C",
          "zip": "94102",
          "state": "CA",
          "first_name": "John",
          "last_name": "Doe"
        },
        "phone": {
          "number": "9876543210",
          "country_code": "+91"
        },
        "email": "test@juspay.in"
      }
    },
    "browser_info": {
        "user_agent": "Dalvik/2.1.0",
        "referer": "abcd.com",
        "ip_address": "157.51.3.204"
    },
    "metadata": {
        "__notes_91_txn_uuid_93_": "12345razorpayirctc1762435588",
        "__notes_91_transaction_id_93_": "razorpayirctc1762435588"
    },
    "payment_method_subtype": "upi_intent",
    "payment_method_type": "upi",
    "return_raw_connector_response": true
}'

Response :

    "id": "12345_pay_019bdf693bc779709cc585df6975606e",
    "status": "requires_customer_action",
    "amount": {
        "order_amount": 1000,
        "currency": "INR",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null,
        "net_amount": 1000,
        "amount_to_capture": null,
        "amount_capturable": 1000,
        "amount_captured": 0
    },
    "customer_id": null,
    "processor_merchant_id": "irctc1768908514_ni3gUbBR6GcqHdk0JBVa",
    "initiator": null,
    "connector": "phonepe",
    "created": "2026-01-21T07:16:14.411Z",
    "modified_at": "2026-01-21T07:16:15.052Z",
    "payment_method_data": {
        "billing": {
            "address": {
                "city": "San Francisco",
                "country": "IN",
                "line1": "123 Main St",
                "line2": "Apt 4B",
                "line3": "Building C",
                "zip": "94102",
                "state": "CA",
                "first_name": "John",
                "last_name": "Doe",
                "origin_zip": null
            },
            "phone": {
                "number": "9876543210",
                "country_code": "+91"
            },
            "email": "test@juspay.in"
        }
    },
    "payment_method_type": "upi",
    "payment_method_subtype": "upi_intent",
    "connector_transaction_id": null,
    "connector_reference_id": "CREDIT_LINE:1764438444",
    "merchant_connector_id": null,
    "browser_info": null,
    "error": null,
    "shipping": null,
    "billing": null,
    "attempts": null,
    "connector_token_details": null,
    "payment_method_id": null,
    "next_action": null,
    "return_url": "https://google.com/success",
    "authentication_type": null,
    "authentication_type_applied": "no_three_ds",
    "is_iframe_redirection_enabled": null,
    "merchant_reference_id": "razorpayirctc1768979774",
    "raw_connector_response": "{\"success\":true,\"code\":\"PAYMENT_INITIATED\",\"message\":\"Payment Initiated\",\"data\":{\"merchantId\":\"IRCTCUAT\",\"merchantTransactionId\":\"CREDIT_LINE:1764438444\",\"instrumentResponse\":{\"type\":\"UPI_INTENT\",\"intentUrl\":\"upi://pay?pa=IRCTCUAT@ybl&pn=MERCHANT&am=10.00&mam=10.00&tr=CREDIT_LINE:1764438444&tn=Payment%20for%20CREDIT_LINE:1764438444&mc=5311&mode=04&purpose=00&utm_campaign=B2B_PG&utm_medium=IRCTCUAT&utm_source=CREDIT_LINE:1764438444&mcbs=\"}}}",
    "feature_metadata": null,
    "metadata": {
        "__notes_91_txn_uuid_93_": "12345razorpayirctc1762435588",
        "__notes_91_transaction_id_93_": "razorpayirctc1762435588"
    }
}

PSync - Request:

curl --location 'http://localhost:8080/v2/payments/12345_pay_019bdf693bc779709cc585df6975606e' \
--header 'x-profile-id: -' \
--header 'x-merchant-id: -' \
--header 'x-tenant-id: public' \
--header 'Content-Type: application/json' \
--header 'api-key: -' \
--data '{
    "merchant_connector_details": {
        "connector_name": "phonepe",
        "merchant_connector_creds": {
            "auth_type": "SignatureKey",
            "api_key": "IRCTCUAT",
            "key1": "-",
            "api_secret": "-"
        }
    },
    "force_sync" : true,
    "return_raw_connector_response": true
}' ```
Response:

{
"id": "12345_pay_019bdf693bc779709cc585df6975606e",
"status": "succeeded",
"amount": {
"order_amount": 1000,
"currency": "INR",
"shipping_cost": null,
"order_tax_amount": null,
"external_tax_calculation": "skip",
"surcharge_calculation": "skip",
"surcharge_amount": null,
"tax_on_surcharge": null,
"net_amount": 1000,
"amount_to_capture": null,
"amount_capturable": 0,
"amount_captured": 1000
},
"customer_id": null,
"processor_merchant_id": "irctc1768908514_ni3gUbBR6GcqHdk0JBVa",
"initiator": null,
"connector": "phonepe",
"created": "2026-01-21T07:16:14.411Z",
"modified_at": "2026-01-21T07:17:06.773Z",
"payment_method_data": {
"billing": {
"address": {
"city": "San Francisco",
"country": "IN",
"line1": "123 Main St",
"line2": "Apt 4B",
"line3": "Building C",
"zip": "94102",
"state": "CA",
"first_name": "John",
"last_name": "Doe",
"origin_zip": null
},
"phone": {
"number": "9876543210",
"country_code": "+91"
},
"email": "test@juspay.in"
}
},
"payment_method_type": "upi",
"payment_method_subtype": "upi_intent",
"connector_transaction_id": null,
"connector_reference_id": "CREDIT_LINE:1764438444",
"merchant_connector_id": null,
"browser_info": null,
"error": null,
"shipping": null,
"billing": null,
"attempts": null,
"connector_token_details": null,
"payment_method_id": null,
"next_action": null,
"return_url": "https://google.com/success",
"authentication_type": null,
"authentication_type_applied": null,
"is_iframe_redirection_enabled": null,
"merchant_reference_id": "razorpayirctc1768979774",
"raw_connector_response": "{"success":true,"code":"PAYMENT_SUCCESS","message":"Your payment is successful.","data":{"merchantId":"IRCTCUAT","merchantTransactionId":"CREDIT_LINE:1764438444","transactionId":"T2601211246149849246869","amount":1000,"state":"COMPLETED","responseCode":"SUCCESS","paymentInstrument":{"type":"UPI","utr":"206378866112","upiTransactionId":"AXLd8ee55a8fd50452da92639907560b6cd","maskedAccountNumber":"XXXXXXXXXXXX0125","bankId":"HDFC","accountHolderName":"Rajesh Kumar","cardNetwork":null,"accountType":null,"upiCreditLine":true},"metaInfo":null,"feesContext":{"amount":20}}}",
"feature_metadata": null,
"metadata": {
"_notes_91_txn_uuid_93": "12345razorpayirctc1762435588",
"_notes_91_transaction_id_93": "razorpayirctc1762435588"
}
} ```

@kanikac199 kanikac199 requested review from a team as code owners January 21, 2026 07:29
@kanikac199 kanikac199 changed the title feat(cnnector): Phonepe upi cc/cl response handling feat(connector): Phonepe upi cc/cl response handling Jan 21, 2026
@kanikac199 kanikac199 requested a review from a team as a code owner January 21, 2026 13:26
@kanikac199 kanikac199 self-assigned this Jan 27, 2026
if let Some(instrument_response) = &data.instrument_response {
// Detect UPI mode from instrument response
let upi_mode =
determine_upi_mode(instrument_response, data.response_code.as_ref());
Copy link
Contributor

Choose a reason for hiding this comment

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

do we also need this for AuthZ flow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not needed


// Additional payment method data for UPI payments
message UpiConnectorResponse {
optional string upi_mode = 1; // UPI mode detected from connector (e.g., "UPICC", "UPICL", "UPI_ACCOUNT")
Copy link
Contributor

Choose a reason for hiding this comment

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

since this is UPI_SOURCE, use enum here

// Success but no instrument response
// Success but no instrument response - try fallback detection from response_code
let upi_mode = data.response_code.as_ref().and_then(|code| {
if code == "CREDIT_ACCOUNT_NOT_ALLOWED_FOR_SENDER" {
Copy link
Contributor

Choose a reason for hiding this comment

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

use consts for all these hardcoded values

/// Extracts UPI mode from sync response payment instrument
fn extract_upi_mode_from_sync_data(sync_data: &PhonepeSyncResponseData) -> Option<String> {
// Try to parse payment_instrument JSON to extract fields
if let Some(payment_instrument) = sync_data.payment_instrument.as_ref() {
Copy link
Contributor

Choose a reason for hiding this comment

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

change payment_instrument from Option<serde_json::Value> to a struct for better handling

}
}

// Fallback: check top-level fields in sync response
Copy link
Contributor

Choose a reason for hiding this comment

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

use determine_upi_mode instead

fn determine_upi_mode(
instrument_response: &PhonepeInstrumentResponse,
response_code: Option<&String>,
) -> Option<String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

use UpiSource instead of String

@kanikac199 kanikac199 requested a review from a team as a code owner January 29, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants