Skip to content

Conversation

NISHANTH1221
Copy link
Contributor

@NISHANTH1221 NISHANTH1221 commented Jun 4, 2025

Type of Change

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

Description

The following details are needed to be stored in the payment intent feature metadata as a part of transaction monitoring system for revenue recovery.

  1. Card Network
  2. Card Issuer
  3. First Payment Gateway Error Code
  4. First Network Error Code
  5. First Network Advice Code

These will be constant through out the lifecycle of the payment intent in revenue recovery system. We need to use these parameters further in revenue recovery system. We need this as training data and need these while Deciding optimal time for the retry.

These information is stored in payment intent because this information will be constant throughout for payment intent and this information cannot be populated every time we retry this payment.

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?

Can be tested by following the steps mentioned in this PR: #7461. Here are the test results:

{
    "redirect_response": null,
    "search_tags": null,
    "apple_pay_recurring_details": null,
    "payment_revenue_recovery_metadata": {
        "total_retry_count": 2,
        "payment_connector_transmission": "ConnectorCallUnsuccessful",
        "billing_connector_id": "mca_MWigDQEKB7Du4L9YqeSe",
        "active_attempt_payment_connector_id": "mca_PY26kh9v0Kiu1hd7lTzL",
        "billing_connector_payment_details": {
            "payment_processor_token": "card_1RXkE8RpLdtbmMLFiOTVtzLI",
            "connector_customer_id": "cus_SSfYcZWkulAsCG"
        },
        "payment_method_type": "card",
        "payment_method_subtype": "credit",
        "connector": "stripe",
        "invoice_next_billing_time": "2025-07-15 18:30:00.0",
        "card_network": "Visa",
        "card_issuer": null,
        "first_payment_attempt_pg_error_code": "card_declined",
        "first_payment_attempt_network_decline_code": "card_declined",
        "first_payment_attempt_network_advice_code": "card_declined"
    }
}

Or

Run this Curl by filling our:

curl --location 'http://localhost:8080/v2/payments/create-intent' \
--header 'Content-Type: application/json' \
--header 'x-profile-id: Profile_id' \
--header 'Authorization: api-key=api-key' \
--header 'api-key: apikey' \
--data-raw '{
    "amount_details": {
        "order_amount": 100,
        "currency": "USD"
    },
    "capture_method":"manual",
    "authentication_type": "no_three_ds",
    "billing": {
        "address": {
            "first_name": "John",
            "last_name": "Dough"
        },
        "email": "[email protected]"
    },
    "shipping": {
        "address": {
            "first_name": "John",
            "last_name": "Dough",
            "city": "Karwar",
            "zip": "581301",
            "state": "Karnataka"
        },
        "email": "[email protected]"
    },
    "feature_metadata": {
        "payment_revenue_recovery_metadata": {
            "total_retry_count": 2,
            "payment_connector_transmission": "ConnectorCallUnsuccessful", 
            "billing_connector_id": "bca_12345",
            "active_attempt_payment_connector_id": "pca_67890",
            "billing_connector_payment_details": {
                "payment_processor_token": "tok_abcdef123456",
                "connector_customer_id": "cust_98765"
            },
            "payment_method_type": "card",
            "payment_method_subtype": "credit",
            "connector": "stripe",
            "invoice_next_billing_time": "2025-06-12T15:00:00Z",
            "billing_connector_payment_method_details": {
                    "type": "Card",
                    "value": {
                        "card_network": "Visa",
                        "card_issuer": "HDFC Bank"
                    }
                },
            "first_payment_attempt_pg_error_code": "PG001",
            "first_payment_attempt_network_decline_code": "ND002",
            "first_payment_attempt_network_advice_code": "AD003"
        }

    }
}'

This is the expected response

{
    "id": "12345_pay_019763e442877b72aaa3b0a5304b2c4e",
    "status": "requires_payment_method",
    "amount_details": {
        "order_amount": 100,
        "currency": "USD",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null
    },
    "client_secret": "cs_019763e442ad7ff1b735ca04e9810f2c",
    "profile_id": "pro_jhWJnFzJZ4tDqxGRrSbX",
    "merchant_reference_id": null,
    "routing_algorithm_id": null,
    "capture_method": "manual",
    "authentication_type": "no_three_ds",
    "billing": {
        "address": {
            "city": null,
            "country": null,
            "line1": null,
            "line2": null,
            "line3": null,
            "zip": null,
            "state": null,
            "first_name": "John",
            "last_name": "Dough"
        },
        "phone": null,
        "email": "[email protected]"
    },
    "shipping": {
        "address": {
            "city": "Karwar",
            "country": null,
            "line1": null,
            "line2": null,
            "line3": null,
            "zip": "581301",
            "state": "Karnataka",
            "first_name": "John",
            "last_name": "Dough"
        },
        "phone": null,
        "email": "[email protected]"
    },
    "customer_id": null,
    "customer_present": "present",
    "description": null,
    "return_url": null,
    "setup_future_usage": "on_session",
    "apply_mit_exemption": "Skip",
    "statement_descriptor": null,
    "order_details": null,
    "allowed_payment_method_types": null,
    "metadata": null,
    "connector_metadata": null,
    "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "payment_revenue_recovery_metadata": {
            "total_retry_count": 2,
            "payment_connector_transmission": "ConnectorCallUnsuccessful",
            "billing_connector_id": "bca_12345",
            "active_attempt_payment_connector_id": "pca_67890",
            "billing_connector_payment_details": {
                "payment_processor_token": "tok_abcdef123456",
                "connector_customer_id": "cust_98765"
            },
            "payment_method_type": "card",
            "payment_method_subtype": "credit",
            "connector": "stripe",
            "billing_connector_payment_method_details": {
                "type": "Card",
                "value": {
                    "card_network": "Visa",
                    "card_issuer": "HDFC Bank"
                }
            },
            "invoice_next_billing_time": "2025-06-12T15:00:00.000Z",
            "first_payment_attempt_pg_error_code": "PG001",
            "first_payment_attempt_network_decline_code": "ND002",
            "first_payment_attempt_network_advice_code": "AD003"
        }
    },
    "payment_link_enabled": "Skip",
    "payment_link_config": null,
    "request_incremental_authorization": "default",
    "expires_on": "2025-06-12T11:41:33.123Z",
    "frm_metadata": null,
    "request_external_three_ds_authentication": "Skip"
}

Check the DB for feature metadata and verify the data.

Sample log:
Screenshot 2025-06-12 at 5 02 26 PM

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

Summary by CodeRabbit

  • New Features
    • Added support for capturing and displaying additional card metadata, including card network, card issuer, and card ISIN, in payment and revenue recovery data.
    • Enhanced error reporting by including detailed error codes from the first payment attempt, such as payment gateway error codes, network decline codes, and advice codes.
  • Bug Fixes
    • Improved propagation and mapping of card and error information across payment and revenue recovery processes.
  • Chores
    • Updated internal processes to fetch and enrich card issuer information asynchronously during revenue recovery operations.

Nishanth Challa and others added 30 commits April 2, 2025 14:06
Copy link

coderabbitai bot commented Jun 11, 2025

Walkthrough

This change extends payment and revenue recovery data models to include new fields for card network, card issuer, and additional error codes related to payment attempts. It updates struct definitions, API schemas, and transformation logic across multiple modules, introducing asynchronous operations for fetching card issuer details and enhancing metadata propagation throughout the payment and revenue recovery workflows.

Changes

File(s) Change Summary
api-reference-v2/openapi_spec.json Extended PaymentRevenueRecoveryMetadata schema: added required card_network and new optional fields for card issuer and error codes.
crates/api_models/src/payments.rs Added optional card_network, card_issuer, and error code fields to PaymentRevenueRecoveryMetadata and PaymentsAttemptRecordRequest.
crates/diesel_models/src/types.rs Added optional card_network, card_issuer, and error code fields to PaymentRevenueRecoveryMetadata.
crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs Added brand (card network) and iin fields to ChargebeeCardDetails; updated conversion logic to propagate these fields.
crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs Added card_type (card network) and first_six fields to PaymentMethod; updated conversion logic to propagate these fields.
crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs Extended StripeBilling structs to include card network, updated conversion logic, and mapped Stripe card networks to common enum.
crates/hyperswitch_domain_models/src/lib.rs Updated model conversion logic to map new card and error code fields between API and Diesel models.
crates/hyperswitch_domain_models/src/payments.rs Added card_network and card_issuer fields to RevenueRecoveryData; enhanced error code extraction and metadata construction logic.
crates/hyperswitch_domain_models/src/revenue_recovery.rs Added optional card_network and card_isin fields to RevenueRecoveryAttemptData and updated conversion logic.
crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs Added optional card_network and card_isin fields to BillingConnectorPaymentsSyncResponse.
crates/router/src/core/payments/operations/payment_attempt_record.rs Populated card_network and card_issuer in RevenueRecoveryData from request data.
crates/router/src/core/payments/transformers.rs Extended metadata mapping to include new card and error code fields in feature metadata conversion.
crates/router/src/core/revenue_recovery/api.rs Made create_payment_record_request asynchronous and updated error handling.
crates/router/src/core/webhooks/recovery_incoming.rs Refactored to make create_payment_record_request async; fetches card issuer info and includes new card fields in request.

Sequence Diagram(s)

sequenceDiagram
    participant Webhook
    participant RecoveryAttempt
    participant Store
    participant PaymentRecord

    Webhook->>RecoveryAttempt: record_payment_attempt()
    RecoveryAttempt->>RecoveryAttempt: create_payment_record_request(state, ...)
    alt card_isin present
        RecoveryAttempt->>Store: fetch_card_info(card_isin)
        Store-->>RecoveryAttempt: card_info (may include issuer)
        RecoveryAttempt->>RecoveryAttempt: extract card_issuer
    end
    RecoveryAttempt-->>PaymentRecord: PaymentsAttemptRecordRequest (with card_network, card_issuer, error codes)
Loading

Poem

🐇
Cards and codes now hop along,
Networks, issuers, join the throng.
Async bunnies fetch with glee,
More details in your payment spree!
Recovery paths now better tracked—
With every hop, more data packed.
Hooray for metadata, let’s celebrate!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (3)
crates/router/src/core/webhooks/recovery_incoming.rs (1)

618-633: LGTM! Error handling implementation addresses previous review feedback.

The implementation correctly logs the error when fetching card info fails, rather than ignoring it silently, which addresses the previous review comment by srujanchikke.

crates/hyperswitch_domain_models/src/payments.rs (1)

972-1010: Error code extraction logic correctly handles PG and network codes separately.

The implementation correctly retrieves PG error codes and network codes (advice/decline) as separate fields without replacing one with the other. This addresses the concern raised in the previous review comment by srujanchikke.

crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs (1)

562-563: TODO comment appropriately addresses the card issuer implementation challenge.

The comment clearly explains why card issuer details cannot be fetched in the same way as other billing connectors (Stripe doesn't provide card bin info) and indicates that an alternative approach needs to be found. This addresses the previous review comment by srujanchikke.

🧹 Nitpick comments (9)
crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs (1)

31-34: Doc-string typo & mis-spelled acronym

/// card netword network has a duplicated word and ISIN is almost certainly meant to be IIN (Issuer Identification Number). Consider:

-    /// card netword network
+    /// card network
...
-    /// card isin
+    /// card iin (issuer identification number – first 6-8 digits)

This is only documentation so the change is low-risk but improves clarity.

crates/hyperswitch_domain_models/src/revenue_recovery.rs (1)

278-280: Naming consistency – isin vs issuer

These lines use card_isin, whereas the payment-attempt code paths use card_issuer. Decide on one term (card_iin is industry-standard) and refactor. Inconsistent naming will silently drop data when converting between structs.

crates/diesel_models/src/types.rs (1)

177-186: Add skip_serializing_if to optional fields

The newly-added optional fields will always be emitted as null, increasing payload size and breaking some strict-schema consumers. A lightweight tweak keeps the JSON clean:

-    pub card_network: Option<common_enums::enums::CardNetwork>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub card_network: Option<common_enums::enums::CardNetwork>,

(repeat for the remaining Option<String> fields)

Purely cosmetic but improves API ergonomics.

api-reference-v2/openapi_spec.json (1)

17838-17840: Enhance card_network documentation
The card_network property references the CardNetwork schema but lacks its own description and example. Consider enriching it for API consumers. For example:

 "card_network": {
-  "$ref": "#/components/schemas/CardNetwork"
+  "$ref": "#/components/schemas/CardNetwork",
+  "description": "Card network used for the payment method",
+  "example": "VISA"
 }
crates/hyperswitch_domain_models/src/lib.rs (1)

308-315: Round-trip consistency test missing

The convert_back implementation now emits the new metadata, which is great. To avoid regressions, please add a unit test that:

  1. Creates a fully-populated ApiRevenueRecoveryMetadata,
  2. Converts it to PaymentRevenueRecoveryMetadata with convert_from,
  3. Converts it back with convert_back,
  4. Asserts equality with the original.

This will ensure all fields – especially the newly introduced ones – survive the round-trip unchanged.

crates/api_models/src/payments.rs (3)

8687-8691: value_type = Option<String> is redundant and misleading

utoipa automatically interprets Option<T> from the field’s real type. Encoding the option in the value_type is unnecessary and may generate an incorrect schema.

-    #[schema(value_type = Option<String>, example = "JP MORGAN CHASE")]
+    #[schema(example = "JP MORGAN CHASE")]-    #[schema(value_type = Option<String>, example = "card_declined")]
+    #[schema(example = "card_declined")]

Apply the same simplification to all three newly-added String option fields.


8693-8697: Doc string / field-name mismatch (“Error” vs “Decline”)

The comment says “Network Error Code” while the field is named first_payment_attempt_network_decline_code. Pick one terminology across API, DB and docs to avoid confusion for integrators.


8817-8819: Add value_type for consistency or drop from both fields

In the first struct you provide value_type for card_issuer; here it’s omitted. Either add it or remove it from the earlier struct to keep the OpenAPI spec uniform.

crates/hyperswitch_domain_models/src/payments.rs (1)

977-999: Consider simplifying the error code extraction logic.

The nested logic could be more readable by extracting error codes in a single pass.

-        let (first_pg_error_code, first_network_advice_code, first_network_decline_code) =
-            feature_metadata_first_pg_error_code.map_or_else(
-                || {
-                    let first_pg_error_code = self
-                        .payment_attempt
-                        .error
-                        .as_ref()
-                        .map(|error| error.code.clone());
-                    let first_network_advice_code = self
-                        .payment_attempt
-                        .error
-                        .as_ref()
-                        .and_then(|error| error.network_advice_code.clone());
-                    let first_network_decline_code = self
-                        .payment_attempt
-                        .error
-                        .as_ref()
-                        .and_then(|error| error.network_decline_code.clone());
-                    (
-                        first_pg_error_code,
-                        first_network_advice_code,
-                        first_network_decline_code,
-                    )
-                },
-                |pg_code| {
-                    let advice_code = revenue_recovery
-                        .as_ref()
-                        .and_then(|data| data.first_payment_attempt_network_advice_code.clone());
-                    let decline_code = revenue_recovery
-                        .as_ref()
-                        .and_then(|data| data.first_payment_attempt_network_decline_code.clone());
-                    (Some(pg_code), advice_code, decline_code)
-                },
-            );
+        let (first_pg_error_code, first_network_advice_code, first_network_decline_code) =
+            match feature_metadata_first_pg_error_code {
+                Some(pg_code) => {
+                    let revenue_recovery_ref = revenue_recovery.as_ref();
+                    (
+                        Some(pg_code),
+                        revenue_recovery_ref.and_then(|data| data.first_payment_attempt_network_advice_code.clone()),
+                        revenue_recovery_ref.and_then(|data| data.first_payment_attempt_network_decline_code.clone()),
+                    )
+                }
+                None => {
+                    self.payment_attempt.error.as_ref().map_or(
+                        (None, None, None),
+                        |error| (
+                            Some(error.code.clone()),
+                            error.network_advice_code.clone(),
+                            error.network_decline_code.clone(),
+                        )
+                    )
+                }
+            };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b159a1d and 88671ca.

📒 Files selected for processing (14)
  • api-reference-v2/openapi_spec.json (2 hunks)
  • crates/api_models/src/payments.rs (2 hunks)
  • crates/diesel_models/src/types.rs (1 hunks)
  • crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs (2 hunks)
  • crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs (2 hunks)
  • crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs (5 hunks)
  • crates/hyperswitch_domain_models/src/lib.rs (2 hunks)
  • crates/hyperswitch_domain_models/src/payments.rs (4 hunks)
  • crates/hyperswitch_domain_models/src/revenue_recovery.rs (2 hunks)
  • crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs (1 hunks)
  • crates/router/src/core/payments/operations/payment_attempt_record.rs (1 hunks)
  • crates/router/src/core/payments/transformers.rs (1 hunks)
  • crates/router/src/core/revenue_recovery/api.rs (1 hunks)
  • crates/router/src/core/webhooks/recovery_incoming.rs (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Validate generated OpenAPI spec file
  • GitHub Check: Check wasm build
  • GitHub Check: Run Cypress tests
  • GitHub Check: Run tests on stable toolchain (ubuntu-latest)
  • GitHub Check: Check compilation for V2 features
  • GitHub Check: Check compilation on MSRV toolchain (ubuntu-latest)
🔇 Additional comments (11)
crates/router/src/core/payments/operations/payment_attempt_record.rs (1)

195-204:

✅ Verification successful

Double-check card_issuer versus card_isin naming consistency

The new struct fields are cloned in correctly, but elsewhere in the PR (revenue_recovery.rs, connector transformers) the equivalent concept is named card_isin. A silent mismatch between card_issuer here and card_isin elsewhere will compile yet produce None in one half of the pipeline – losing the information you are trying to persist.

-        card_issuer: request.card_issuer.clone(),
+        /* make sure this name matches the field in
+           hyperswitch_domain_models::payments::RevenueRecoveryData
+           and the JSON produced by connectors */

Please audit the entire codebase (or run rg card_issuer / rg card_isin) and align the nomenclature to a single term.


🏁 Script executed:

#!/bin/bash
# Check for inconsistent usage of card_issuer vs card_isin
rg --line-number -e 'card_issuer' -e 'card_isin'

Length of output: 55741


🏁 Script executed:

#!/bin/bash
# Locate and display the definition of RevenueRecoveryData in the domain models
rg --context 5 "struct RevenueRecoveryData" -n crates/hyperswitch_domain_models/src

Length of output: 1052


🏁 Script executed:

#!/bin/bash
# Display full definition of RevenueRecoveryData to verify its fields
sed -n '940,1000p' crates/hyperswitch_domain_models/src/payments.rs

Length of output: 2537


No naming mismatch between card_issuer and card_isin

The RevenueRecoveryData struct in crates/hyperswitch_domain_models/src/payments.rs defines and serializes only a card_issuer field (there is no card_isin on that type), and the code in payment_attempt_record.rs correctly maps to it. Downstream transformer code also references card_issuer. Since card_issuer and card_isin represent different pieces of metadata and there is no inconsistent use of the same concept under two names here, no changes are required.

crates/router/src/core/revenue_recovery/api.rs (1)

186-202:

✅ Verification successful

Potential stale call-sites after async signature change

create_payment_record_request is now async and takes state. This call site is updated, but other invocations may still use the old sync signature, leading to compilation failures.


🏁 Script executed:

#!/bin/bash
# locate other call-sites that still use the old signature
rg --line-number 'create_payment_record_request\s*\(' | grep -v 'state'

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Inspect call-sites of create_payment_record_request in recovery_incoming.rs to verify inclusion of `state`
rg --line-number '\.create_payment_record_request' -C3 crates/router/src/core/webhooks/recovery_incoming.rs

Length of output: 520


All invocations updated to the new async signature
Both call-sites in api.rs and recovery_incoming.rs now include state as the first argument, and there are no remaining stale usages of create_payment_record_request.

crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs (1)

209-211: Handle absent card metadata & naming consistency

If the above fields become Option, this assignment will not compile.
Additionally, the target struct uses card_isin, which is semantically an IIN/BIN. The double mis-naming may confuse future readers.

-                    card_network: Some(item.response.payment_method.card_type),
-                    card_isin:   Some(item.response.payment_method.first_six),
+                    card_network: item.response.payment_method.card_type,
+                    card_isin:    item.response.payment_method.card_iin,

Please update the downstream struct definition (or rename to card_iin) for coherence.

api-reference-v2/openapi_spec.json (4)

17791-17792: Validate breaking change for required fields
Adding "connector" and "card_network" to the required array makes these properties mandatory. Confirm that both fields are defined under properties and that all existing API consumers have been updated to supply them to avoid a breaking change.


17841-17846: card_issuer schema property
The card_issuer field is well-defined with type, description, example, and nullable: true. No changes needed.


17847-17852: first_payment_attempt_pg_error_code property
The first payment gateway error code is correctly defined with a clear description, example, and nullable: true.


17859-17864: first_payment_attempt_network_advice_code property
This field is consistently named and documented with an example and nullable: true. No changes required.

crates/router/src/core/payments/transformers.rs (1)

4948-4958:

❓ Verification inconclusive

Ensure correct mapping of new revenue recovery fields
The new card_issuer, card_network, and first-attempt error code fields are properly cloned from the source metadata. Confirm that the source struct (diesel_models::types::PaymentRevenueRecoveryMetadata) declares these fields with the intended types and that the API model (api_models::payments::PaymentRevenueRecoveryMetadata) matches the updated OpenAPI schema (especially noting that card_network is now required).

Run the following to verify definitions align across modules:


🏁 Script executed:

#!/bin/bash
# Verify source model has these fields
rg -n "struct PaymentRevenueRecoveryMetadata" -A3 crates/diesel_models/src/types.rs
# Verify API model declaration
rg -n "struct PaymentRevenueRecoveryMetadata" -A10 crates/api_models/src/payments.rs

Length of output: 1073


🏁 Script executed:

#!/bin/bash
set -e

echo "---- diesel_models/src/types.rs ----"
rg -n "card_issuer" -A2 crates/diesel_models/src/types.rs
rg -n "card_network" -A2 crates/diesel_models/src/types.rs
rg -n "first_payment_attempt_network_advice_code" -A2 crates/diesel_models/src/types.rs
rg -n "first_payment_attempt_network_decline_code" -A2 crates/diesel_models/src/types.rs
rg -n "first_payment_attempt_pg_error_code" -A2 crates/diesel_models/src/types.rs

echo ""
echo "---- api_models/src/payments.rs ----"
rg -n "card_issuer" -A2 crates/api_models/src/payments.rs
rg -n "card_network" -A2 crates/api_models/src/payments.rs
rg -n "first_payment_attempt_network_advice_code" -A2 crates/api_models/src/payments.rs
rg -n "first_payment_attempt_network_decline_code" -A2 crates/api_models/src/payments.rs
rg -n "first_payment_attempt_pg_error_code" -A2 crates/api_models/src/payments.rs

Length of output: 6355


Verify API and Diesel model field alignment
Both the Diesel and API models now declare the new revenue-recovery fields:

• In crates/diesel_models/src/types.rs, PaymentRevenueRecoveryMetadata includes:

  • card_network: Option<common_enums::enums::CardNetwork>
  • card_issuer: Option<String>
  • first_payment_attempt_network_decline_code: Option<String>
  • first_payment_attempt_network_advice_code: Option<String>
  • first_payment_attempt_pg_error_code: Option<String>

• In crates/api_models/src/payments.rs, PaymentRevenueRecoveryMetadata declares:

  • card_network: Option<api_enums::CardNetwork>
  • card_issuer: Option<String>
  • first_payment_attempt_network_decline_code: Option<String>
  • first_payment_attempt_network_advice_code: Option<String>
  • first_payment_attempt_pg_error_code: Option<String>

Please confirm these definitions align with the updated OpenAPI schema—particularly whether card_network is now required (and thus should be a non-optional field in the API model).

crates/hyperswitch_domain_models/src/lib.rs (1)

285-292:

✅ Verification successful

Confirm symmetry & default-handling for newly-added fields

payment_connector_transmission is still normalised with unwrap_or_default(), whereas the newly-added fields are transferred verbatim.
If any of these new fields are Option<T> in the API model but non-optional in the Diesel model (or vice-versa), the current pass-through can panic at runtime or silently store NULLs. Please double-check the underlying struct definitions and add unwrap_or_default() / unwrap_or(…) as appropriate to keep the conversion lossless and consistent with the older fields.
Adding a conversion-roundtrip unit test here would lock in the expected behaviour.


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Show mapping context around the newly-added fields
rg -n "card_issuer" -C5 crates/hyperswitch_domain_models/src/lib.rs

# Show nearby `impl From<...>` blocks to identify source/target types
rg -n "impl From<" -C3 crates/hyperswitch_domain_models/src/lib.rs

Length of output: 2782


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Locate where payment_connector_transmission is mapped
rg -n "payment_connector_transmission" -C5 crates/hyperswitch_domain_models/src/lib.rs

# Locate usage of unwrap_or_default in the same file for context comparison
rg -n "unwrap_or_default" -C5 crates/hyperswitch_domain_models/src/lib.rs

Length of output: 2196


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Display the full convert_from implementation around the card_issuer mapping
sed -n '240,320p' crates/hyperswitch_domain_models/src/lib.rs

Length of output: 3922


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Locate struct definitions for domain model and API model
rg -n "struct PaymentRevenueRecoveryMetadata" -C5 crates/hyperswitch_domain_models/src/lib.rs
rg -R "ApiRevenueRecoveryMetadata" -n .

Length of output: 114


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# List all crates in the workspace
echo "Crates in workspace:" 
ls crates

# Find domain model struct definition
echo -e "\nDomain model struct PaymentRevenueRecoveryMetadata definition:"
rg -n "struct PaymentRevenueRecoveryMetadata" -C5 crates

# Find API model struct definition
echo -e "\nAPI model struct ApiRevenueRecoveryMetadata definition:"
rg -n "struct ApiRevenueRecoveryMetadata" -C5 crates

Length of output: 2701


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Locate the import or alias for ApiRevenueRecoveryMetadata in hyperswitch_domain_models
rg -n "ApiRevenueRecoveryMetadata" -C5 crates/hyperswitch_domain_models/src/lib.rs

Length of output: 1802


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Show the domain (Diesel) struct definition with all fields
rg -n "pub struct PaymentRevenueRecoveryMetadata" -C20 crates/diesel_models/src/types.rs

# Show the API model struct definition
rg -n "pub struct PaymentRevenueRecoveryMetadata" -C20 crates/api_models/src/payments.rs

Length of output: 4221


🏁 Script executed:

#!/bin/bash
set -eo pipefail

# Full Diesel model struct PaymentRevenueRecoveryMetadata
echo "=== Diesel model ==="
sed -n '155,260p' crates/diesel_models/src/types.rs

# Full API model struct PaymentRevenueRecoveryMetadata
echo -e "\n=== API model ==="
sed -n '8657,8750p' crates/api_models/src/payments.rs

Length of output: 6557


No action needed: Optional fields map symmetrically

A review of the struct definitions shows that all newly-added fields (card_issuer, card_network, first_payment_attempt_pg_error_code, first_payment_attempt_network_decline_code, first_payment_attempt_network_advice_code) are declared as Option<T> in both ApiRevenueRecoveryMetadata and the Diesel PaymentRevenueRecoveryMetadata. The direct pass-through in convert_from/convert_back preserves their optional semantics without risk of panics or silent NULL mismatches—no unwrap_or_default is required here.

• Location:

  • Mapping in crates/hyperswitch_domain_models/src/lib.rs, lines 285–292
  • Structs in crates/api_models/src/payments.rs and crates/diesel_models/src/types.rs

Optional: you may still add a conversion-roundtrip unit test to lock in this behaviour and guard against future mismatches.

crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs (2)

442-442: Comprehensive card network support implementation.

The implementation provides thorough coverage of card networks with proper enum variants and conversion logic.

Also applies to: 465-483, 639-659


534-535:

✅ Verification successful

Verify that payment_intent is the correct field for connector_transaction_id.

The change from charge_id to payment_intent for the connector transaction ID might affect transaction tracking. Please ensure this aligns with Stripe's recommended practice for identifying transactions in billing contexts.


🌐 Web query:

What is the difference between charge_id and payment_intent in Stripe, and which one should be used as the primary transaction identifier for Stripe Billing webhooks?

💡 Result:

In Stripe's payment processing model, both charge_id and payment_intent are important but serve different purposes, especially when dealing with Stripe Billing and webhooks.

Charge ID (charge_id):

  • A charge_id represents a single charge made to a payment method.
  • It is created as part of a payment operation.
  • Each charge corresponds to a single payment attempt.
  • You may have multiple charges per payment if, for example, you do separate charges or retries.
  • In older Stripe integrations (before Payment Intents were introduced), the charge object was the primary way to track transactions.

Payment Intent (payment_intent):

  • Introduced with Stripe’s newer payment model to support more advanced payment flows (like SCA – Strong Customer Authentication).
  • A payment_intent represents the entire lifecycle of a payment on a customer’s intent to pay.
  • It can have multiple associated charges during retries or failure/success flows.
  • Acts as a more holistic representation of a transaction from initiation to completion.
  • Used extensively in Stripe Billing, Subscriptions, and newer payment flows.

Which one to use as the primary transaction identifier for Stripe Billing webhooks?

  • Use the payment_intent ID as the primary transaction identifier.
  • The payment_intent ID provides a comprehensive view of the payment process and is the newer, more robust way to track payments.
  • Stripe Billing webhooks (like invoice.payment_succeeded) typically include the payment_intent ID, which you should use to reconcile payments.
  • The charge_id can vary or have multiple instances per payment, making it less reliable as a primary transaction reference compared to the payment_intent.

Summary:

  • Charge ID = single payment attempt/charge.
  • Payment Intent = entire payment lifecycle (preferred for billing).
  • For Stripe Billing webhooks, always use the payment_intent ID as the primary transaction identifier.

If you want me to, I can provide example webhook payloads or further details on handling these IDs in your integration.


Confirmed: payment_intent Is the Right Transaction Identifier

This change aligns with Stripe’s recommendation to use the PaymentIntent ID as the primary identifier for Billing webhooks—providing a holistic view of the payment lifecycle. No further updates are needed.

• File: crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs
• Lines: 534–535

Comment on lines 17853 to 17858
"first_payment_attempt_network_decline_code": {
"type": "string",
"description": "First Payment Attempt Network Error Code",
"example": "05",
"nullable": true
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align naming and description for network error code
The key first_payment_attempt_network_decline_code uses “decline” but the description reads “Network Error Code.” For consistency, either rename the field to first_payment_attempt_network_error_code or adjust the description to “Network Decline Code.” Example renaming diff:

- "first_payment_attempt_network_decline_code": {
+ "first_payment_attempt_network_error_code": {
    "type": "string",
-   "description": "First Payment Attempt Network Error Code",
+   "description": "First Payment Attempt Network Error Code",
    "example": "05",
    "nullable": true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"first_payment_attempt_network_decline_code": {
"type": "string",
"description": "First Payment Attempt Network Error Code",
"example": "05",
"nullable": true
},
"first_payment_attempt_network_error_code": {
"type": "string",
"description": "First Payment Attempt Network Error Code",
"example": "05",
"nullable": true
},
🤖 Prompt for AI Agents
In api-reference-v2/openapi_spec.json around lines 17853 to 17858, the field
name 'first_payment_attempt_network_decline_code' and its description are
inconsistent; the name uses "decline" while the description says "Network Error
Code." To fix this, either rename the field to
'first_payment_attempt_network_error_code' to match the description or change
the description to "Network Decline Code" to match the field name, ensuring both
are aligned in terminology.

Comment on lines 8683 to 8685
#[schema(value_type = CardNetwork, example = "Visa")]
/// Card Network
pub card_network: Option<common_enums::CardNetwork>,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unqualified CardNetwork path can break utoipa macro resolution

CardNetwork lives under common_enums. Inside the #[schema] attribute the path must be fully-qualified (or imported into the module prelude) for the derive macro to compile.

-    #[schema(value_type = CardNetwork, example = "Visa")]
+    #[schema(value_type = common_enums::CardNetwork, example = "Visa")]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[schema(value_type = CardNetwork, example = "Visa")]
/// Card Network
pub card_network: Option<common_enums::CardNetwork>,
#[schema(value_type = common_enums::CardNetwork, example = "Visa")]
/// Card Network
pub card_network: Option<common_enums::CardNetwork>,
🤖 Prompt for AI Agents
In crates/api_models/src/payments.rs at lines 8683 to 8685, the #[schema]
attribute uses an unqualified CardNetwork type which can cause utoipa macro
resolution to fail. Fix this by fully qualifying the CardNetwork path inside the
#[schema(value_type = ...)] attribute as common_enums::CardNetwork or ensure it
is properly imported into the module prelude before usage.

@@ -8680,6 +8680,21 @@ pub struct PaymentRevenueRecoveryMetadata {
pub connector: common_enums::connector_enums::Connector,
/// Invoice Next billing time
pub invoice_next_billing_time: Option<PrimitiveDateTime>,
#[schema(value_type = CardNetwork, example = "Visa")]
/// Card Network
pub card_network: Option<common_enums::CardNetwork>,
Copy link
Member

Choose a reason for hiding this comment

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

here the payment_method data is flatten and currently it is specific to cards, when the payment_method is going to change, these field is going to be None always, so change the data structure such a way where specific payment method fields are not flattened

jarnura
jarnura previously approved these changes Jun 12, 2025
srujanchikke
srujanchikke previously approved these changes Jun 12, 2025
@NISHANTH1221 NISHANTH1221 dismissed stale reviews from jarnura and srujanchikke via ba0f74d June 13, 2025 07:20
@hyperswitch-bot hyperswitch-bot bot removed the M-api-contract-changes Metadata: This PR involves API contract changes label Jun 13, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Jun 16, 2025
Merged via the queue into main with commit abe9708 Jun 16, 2025
15 of 20 checks passed
@likhinbopanna likhinbopanna deleted the add_card_information_for_billing_connectors branch June 16, 2025 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat(core): consume card details from billing connectors and first error codes and store them in payment intent table
4 participants