diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 877fb89ca9..9a8bebc893 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -6780,6 +6780,23 @@ } } }, + "BillingConnectorAdditionalCardInfo": { + "type": "object", + "required": [ + "card_network" + ], + "properties": { + "card_network": { + "$ref": "#/components/schemas/CardNetwork" + }, + "card_issuer": { + "type": "string", + "description": "Card Issuer", + "example": "JP MORGAN CHASE", + "nullable": true + } + } + }, "BillingConnectorPaymentDetails": { "type": "object", "required": [ @@ -6797,6 +6814,31 @@ } } }, + "BillingConnectorPaymentMethodDetails": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "card" + ] + }, + "value": { + "$ref": "#/components/schemas/BillingConnectorAdditionalCardInfo" + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, "BlikBankRedirectAdditionalData": { "type": "object", "properties": { @@ -17854,7 +17896,8 @@ "billing_connector_payment_details", "payment_method_type", "payment_method_subtype", - "connector" + "connector", + "billing_connector_payment_method_details" ], "properties": { "total_retry_count": { @@ -17894,11 +17937,32 @@ "connector": { "$ref": "#/components/schemas/Connector" }, + "billing_connector_payment_method_details": { + "$ref": "#/components/schemas/BillingConnectorPaymentMethodDetails" + }, "invoice_next_billing_time": { "type": "string", "format": "date-time", "description": "Invoice Next billing time", "nullable": true + }, + "first_payment_attempt_pg_error_code": { + "type": "string", + "description": "First Payment Attempt Payment Gateway Error Code", + "example": "card_declined", + "nullable": true + }, + "first_payment_attempt_network_decline_code": { + "type": "string", + "description": "First Payment Attempt Network Error Code", + "example": "05", + "nullable": true + }, + "first_payment_attempt_network_advice_code": { + "type": "string", + "description": "First Payment Attempt Network Advice Code", + "example": "02", + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index c37690a9f5..ef4d417ddc 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -8694,9 +8694,41 @@ pub struct PaymentRevenueRecoveryMetadata { /// The name of the payment connector through which the payment attempt was made. #[schema(value_type = Connector, example = "stripe")] pub connector: common_enums::connector_enums::Connector, + #[schema(value_type = BillingConnectorPaymentMethodDetails)] + /// Extra Payment Method Details that are needed to be stored + pub billing_connector_payment_method_details: Option, /// Invoice Next billing time + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub invoice_next_billing_time: Option, + /// First Payment Attempt Payment Gateway Error Code + #[schema(value_type = Option, example = "card_declined")] + pub first_payment_attempt_pg_error_code: Option, + /// First Payment Attempt Network Error Code + #[schema(value_type = Option, example = "05")] + pub first_payment_attempt_network_decline_code: Option, + /// First Payment Attempt Network Advice Code + #[schema(value_type = Option, example = "02")] + pub first_payment_attempt_network_advice_code: Option, } + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum BillingConnectorPaymentMethodDetails { + Card(BillingConnectorAdditionalCardInfo), +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct BillingConnectorAdditionalCardInfo { + #[schema(value_type = CardNetwork, example = "Visa")] + /// Card Network + pub card_network: Option, + #[schema(value_type = Option, example = "JP MORGAN CHASE")] + /// Card Issuer + pub card_issuer: Option, +} + #[cfg(feature = "v2")] impl PaymentRevenueRecoveryMetadata { pub fn set_payment_transmission_field_for_api_request( @@ -8810,6 +8842,14 @@ pub struct PaymentsAttemptRecordRequest { /// source where the payment was triggered by #[schema(value_type = TriggeredBy, example = "internal" )] pub triggered_by: common_enums::TriggeredBy, + + #[schema(value_type = CardNetwork, example = "Visa" )] + /// card_network + pub card_network: Option, + + #[schema(example = "Chase")] + /// Card Issuer + pub card_issuer: Option, } /// Error details for the payment diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 696cc22783..e3efd3dae2 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -174,6 +174,14 @@ pub struct PaymentRevenueRecoveryMetadata { pub connector: common_enums::connector_enums::Connector, /// Time at which next invoice will be created pub invoice_next_billing_time: Option, + /// Extra Payment Method Details that are needed to be stored + pub billing_connector_payment_method_details: Option, + /// First Payment Attempt Payment Gateway Error Code + pub first_payment_attempt_pg_error_code: Option, + /// First Payment Attempt Network Error Code + pub first_payment_attempt_network_decline_code: Option, + /// First Payment Attempt Network Advice Code + pub first_payment_attempt_network_advice_code: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -184,3 +192,19 @@ pub struct BillingConnectorPaymentDetails { /// Billing Connector's Customer Id pub connector_customer_id: String, } + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum BillingConnectorPaymentMethodDetails { + Card(BillingConnectorAdditionalCardInfo), +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct BillingConnectorAdditionalCardInfo { + /// Card Network + pub card_network: Option, + /// Card Issuer + pub card_issuer: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs index ed978d8490..6436cd952e 100644 --- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs @@ -335,6 +335,8 @@ pub struct ChargebeePaymentMethodDetails { #[derive(Serialize, Deserialize, Debug)] pub struct ChargebeeCardDetails { funding_type: ChargebeeFundingType, + brand: common_enums::CardNetwork, + iin: String, } #[derive(Serialize, Deserialize, Debug)] @@ -505,6 +507,8 @@ impl TryFrom for revenue_recovery::RevenueRecoveryAttemptD network_error_message: None, retry_count, invoice_next_billing_time, + card_network: Some(payment_method_details.card.brand), + card_isin: Some(payment_method_details.card.iin), }) } } diff --git a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs index 673ba9d432..9ffd900dc4 100644 --- a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs @@ -136,6 +136,8 @@ pub struct PaymentMethod { pub gateway_token: String, pub funding_source: RecurlyFundingTypes, pub object: RecurlyPaymentObject, + pub card_type: common_enums::CardNetwork, + pub first_six: String, } #[derive(Debug, Serialize, Deserialize)] @@ -204,6 +206,8 @@ impl payment_method_type: common_enums::PaymentMethod::from( item.response.payment_method.object, ), + card_network: Some(item.response.payment_method.card_type), + card_isin: Some(item.response.payment_method.first_six), }, ), ..item.data diff --git a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs index ffefb74e7e..1352b874c8 100644 --- a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs @@ -439,6 +439,7 @@ pub struct StripebillingLatestChargeData { pub payment_method_details: StripePaymentMethodDetails, #[serde(rename = "invoice")] pub invoice_id: String, + pub payment_intent: String, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -446,7 +447,7 @@ pub struct StripePaymentMethodDetails { #[serde(rename = "type")] pub type_of_payment_method: StripebillingPaymentMethod, #[serde(rename = "card")] - pub card_funding_type: StripeCardFundingTypeDetails, + pub card_details: StripeBillingCardDetails, } #[derive(Serialize, Deserialize, Debug, Clone, Copy)] @@ -456,10 +457,31 @@ pub enum StripebillingPaymentMethod { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct StripeCardFundingTypeDetails { +pub struct StripeBillingCardDetails { + pub network: StripebillingCardNetwork, pub funding: StripebillingFundingTypes, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum StripebillingCardNetwork { + Visa, + Mastercard, + AmericanExpress, + JCB, + DinersClub, + Discover, + CartesBancaires, + UnionPay, + Interac, + RuPay, + Maestro, + Star, + Pulse, + Accel, + Nyce, +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] #[serde(rename = "snake_case")] pub enum StripebillingFundingTypes { @@ -509,7 +531,7 @@ impl field_name: "invoice_id", })?; let connector_transaction_id = Some(common_utils::types::ConnectorTransactionId::from( - charge_details.charge_id, + charge_details.payment_intent, )); Ok(Self { @@ -529,14 +551,16 @@ impl connector_customer_id: charge_details.customer, transaction_created_at: Some(charge_details.created), payment_method_sub_type: common_enums::PaymentMethodType::from( - charge_details - .payment_method_details - .card_funding_type - .funding, + charge_details.payment_method_details.card_details.funding, ), payment_method_type: common_enums::PaymentMethod::from( charge_details.payment_method_details.type_of_payment_method, ), + card_network: Some(common_enums::CardNetwork::from( + charge_details.payment_method_details.card_details.network, + )), + // Todo: Fetch Card issuer details. Generally in the other billing connector we are getting card_issuer using the card bin info. But stripe dosent provide any such details. We should find a way for stripe billing case + card_isin: None, }, ), ..item.data @@ -611,3 +635,25 @@ impl }) } } + +impl From for enums::CardNetwork { + fn from(item: StripebillingCardNetwork) -> Self { + match item { + StripebillingCardNetwork::Visa => Self::Visa, + StripebillingCardNetwork::Mastercard => Self::Mastercard, + StripebillingCardNetwork::AmericanExpress => Self::AmericanExpress, + StripebillingCardNetwork::JCB => Self::JCB, + StripebillingCardNetwork::DinersClub => Self::DinersClub, + StripebillingCardNetwork::Discover => Self::Discover, + StripebillingCardNetwork::CartesBancaires => Self::CartesBancaires, + StripebillingCardNetwork::UnionPay => Self::UnionPay, + StripebillingCardNetwork::Interac => Self::Interac, + StripebillingCardNetwork::RuPay => Self::RuPay, + StripebillingCardNetwork::Maestro => Self::Maestro, + StripebillingCardNetwork::Star => Self::Star, + StripebillingCardNetwork::Pulse => Self::Pulse, + StripebillingCardNetwork::Accel => Self::Accel, + StripebillingCardNetwork::Nyce => Self::Nyce, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 99ef5e3581..4ee8620e35 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -55,7 +55,9 @@ use api_models::payments::{ }; #[cfg(feature = "v2")] use api_models::payments::{ + BillingConnectorAdditionalCardInfo as ApiBillingConnectorAdditionalCardInfo, BillingConnectorPaymentDetails as ApiBillingConnectorPaymentDetails, + BillingConnectorPaymentMethodDetails as ApiBillingConnectorPaymentMethodDetails, PaymentRevenueRecoveryMetadata as ApiRevenueRecoveryMetadata, }; use diesel_models::types::{ @@ -63,7 +65,10 @@ use diesel_models::types::{ OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, }; #[cfg(feature = "v2")] -use diesel_models::types::{BillingConnectorPaymentDetails, PaymentRevenueRecoveryMetadata}; +use diesel_models::types::{ + BillingConnectorAdditionalCardInfo, BillingConnectorPaymentDetails, + BillingConnectorPaymentMethodDetails, PaymentRevenueRecoveryMetadata, +}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { @@ -267,6 +272,44 @@ impl ApiModelToDieselModelConvertor for ApplePayRec } } +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for BillingConnectorAdditionalCardInfo +{ + fn convert_from(from: ApiBillingConnectorAdditionalCardInfo) -> Self { + Self { + card_issuer: from.card_issuer, + card_network: from.card_network, + } + } + + fn convert_back(self) -> ApiBillingConnectorAdditionalCardInfo { + ApiBillingConnectorAdditionalCardInfo { + card_issuer: self.card_issuer, + card_network: self.card_network, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for BillingConnectorPaymentMethodDetails +{ + fn convert_from(from: ApiBillingConnectorPaymentMethodDetails) -> Self { + match from { + ApiBillingConnectorPaymentMethodDetails::Card(data) => { + Self::Card(BillingConnectorAdditionalCardInfo::convert_from(data)) + } + } + } + + fn convert_back(self) -> ApiBillingConnectorPaymentMethodDetails { + match self { + Self::Card(data) => ApiBillingConnectorPaymentMethodDetails::Card(data.convert_back()), + } + } +} + #[cfg(feature = "v2")] impl ApiModelToDieselModelConvertor for PaymentRevenueRecoveryMetadata { fn convert_from(from: ApiRevenueRecoveryMetadata) -> Self { @@ -282,6 +325,14 @@ impl ApiModelToDieselModelConvertor for PaymentReven payment_method_subtype: from.payment_method_subtype, connector: from.connector, invoice_next_billing_time: from.invoice_next_billing_time, + billing_connector_payment_method_details: from + .billing_connector_payment_method_details + .map(BillingConnectorPaymentMethodDetails::convert_from), + first_payment_attempt_network_advice_code: from + .first_payment_attempt_network_advice_code, + first_payment_attempt_network_decline_code: from + .first_payment_attempt_network_decline_code, + first_payment_attempt_pg_error_code: from.first_payment_attempt_pg_error_code, } } @@ -298,6 +349,14 @@ impl ApiModelToDieselModelConvertor for PaymentReven payment_method_subtype: self.payment_method_subtype, connector: self.connector, invoice_next_billing_time: self.invoice_next_billing_time, + billing_connector_payment_method_details: self + .billing_connector_payment_method_details + .map(|data| data.convert_back()), + first_payment_attempt_network_advice_code: self + .first_payment_attempt_network_advice_code, + first_payment_attempt_network_decline_code: self + .first_payment_attempt_network_decline_code, + first_payment_attempt_pg_error_code: self.first_payment_attempt_pg_error_code, } } } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index e8ec7041f6..d460cfa466 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -743,6 +743,8 @@ impl PaymentIntent { network_error_message: None, retry_count: None, invoice_next_billing_time: None, + card_isin: None, + card_network: None, }) } @@ -951,6 +953,8 @@ pub struct RevenueRecoveryData { pub retry_count: Option, pub invoice_next_billing_time: Option, pub triggered_by: storage_enums::enums::TriggeredBy, + pub card_network: Option, + pub card_issuer: Option, } #[cfg(feature = "v2")] @@ -962,9 +966,57 @@ where &self, ) -> CustomResult, errors::api_error_response::ApiErrorResponse> { let payment_intent_feature_metadata = self.payment_intent.get_feature_metadata(); - let revenue_recovery = self.payment_intent.get_revenue_recovery_metadata(); let payment_attempt_connector = self.payment_attempt.connector.clone(); + + let feature_metadata_first_pg_error_code = revenue_recovery + .as_ref() + .and_then(|data| data.first_payment_attempt_pg_error_code.clone()); + + 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 billing_connector_payment_method_details = Some( + diesel_models::types::BillingConnectorPaymentMethodDetails::Card( + diesel_models::types::BillingConnectorAdditionalCardInfo { + card_network: self.revenue_recovery_data.card_network.clone(), + card_issuer: self.revenue_recovery_data.card_issuer.clone(), + }, + ), + ); + let payment_revenue_recovery_metadata = match payment_attempt_connector { Some(connector) => Some(diesel_models::types::PaymentRevenueRecoveryMetadata { // Update retry count by one. @@ -999,6 +1051,10 @@ where errors::api_error_response::ApiErrorResponse::InternalServerError })?, invoice_next_billing_time: self.revenue_recovery_data.invoice_next_billing_time, + billing_connector_payment_method_details, + first_payment_attempt_network_advice_code: first_network_advice_code, + first_payment_attempt_network_decline_code: first_network_decline_code, + first_payment_attempt_pg_error_code: first_pg_error_code, }), None => Err(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Connector not found in payment attempt")?, diff --git a/crates/hyperswitch_domain_models/src/revenue_recovery.rs b/crates/hyperswitch_domain_models/src/revenue_recovery.rs index 82692047e4..529c035eeb 100644 --- a/crates/hyperswitch_domain_models/src/revenue_recovery.rs +++ b/crates/hyperswitch_domain_models/src/revenue_recovery.rs @@ -48,6 +48,10 @@ pub struct RevenueRecoveryAttemptData { pub retry_count: Option, /// Time when next invoice will be generated which will be equal to the end time of the current invoice pub invoice_next_billing_time: Option, + /// card network type + pub card_network: Option, + /// card isin + pub card_isin: Option, } /// This is unified struct for Revenue Recovery Invoice Data and it is constructed from billing connectors @@ -271,6 +275,8 @@ impl network_error_message: None, retry_count: invoice_details.retry_count, invoice_next_billing_time: invoice_details.next_billing_at, + card_network: billing_connector_payment_details.card_network.clone(), + card_isin: billing_connector_payment_details.card_isin.clone(), } } } diff --git a/crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs b/crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs index 598795fa11..8dd6aa2e74 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types/revenue_recovery.rs @@ -28,6 +28,10 @@ pub struct BillingConnectorPaymentsSyncResponse { pub payment_method_type: common_enums::enums::PaymentMethod, /// payment method sub type of the payment attempt. pub payment_method_sub_type: common_enums::enums::PaymentMethodType, + /// card netword network + pub card_network: Option, + /// card isin + pub card_isin: Option, } #[derive(Debug, Clone)] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 5b5ed7a83e..d8e08a296d 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -351,6 +351,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AliPayQr, api_models::payments::PaymentAttemptFeatureMetadata, api_models::payments::PaymentAttemptRevenueRecoveryData, + api_models::payments::BillingConnectorPaymentMethodDetails, + api_models::payments::BillingConnectorAdditionalCardInfo, api_models::payments::AliPayRedirection, api_models::payments::MomoRedirection, api_models::payments::TouchNGoRedirection, diff --git a/crates/router/src/core/payments/operations/payment_attempt_record.rs b/crates/router/src/core/payments/operations/payment_attempt_record.rs index 33848e1c39..b2834f0bfa 100644 --- a/crates/router/src/core/payments/operations/payment_attempt_record.rs +++ b/crates/router/src/core/payments/operations/payment_attempt_record.rs @@ -199,6 +199,8 @@ impl retry_count: request.retry_count, invoice_next_billing_time: request.invoice_next_billing_time, triggered_by: request.triggered_by, + card_network: request.card_network.clone(), + card_issuer: request.card_issuer.clone(), }; let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new( payment_intent diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 042fb6ba94..f467eb39dd 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -4900,6 +4900,22 @@ impl ForeignFrom<&diesel_models::types::BillingConnectorPaymentDetails> } } +#[cfg(feature = "v2")] +impl ForeignFrom<&diesel_models::types::BillingConnectorPaymentMethodDetails> + for api_models::payments::BillingConnectorPaymentMethodDetails +{ + fn foreign_from(metadata: &diesel_models::types::BillingConnectorPaymentMethodDetails) -> Self { + match metadata { + diesel_models::types::BillingConnectorPaymentMethodDetails::Card(card_details) => { + Self::Card(api_models::payments::BillingConnectorAdditionalCardInfo { + card_issuer: card_details.card_issuer.clone(), + card_network: card_details.card_network.clone(), + }) + } + } + } +} + #[cfg(feature = "v2")] impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::ErrorDetails> for api_models::payments::ErrorDetails @@ -4965,6 +4981,17 @@ impl ForeignFrom<&diesel_models::types::FeatureMetadata> for api_models::payment ), invoice_next_billing_time: payment_revenue_recovery_metadata .invoice_next_billing_time, + billing_connector_payment_method_details:payment_revenue_recovery_metadata + .billing_connector_payment_method_details.as_ref().map(api_models::payments::BillingConnectorPaymentMethodDetails::foreign_from), + first_payment_attempt_network_advice_code: payment_revenue_recovery_metadata + .first_payment_attempt_network_advice_code + .clone(), + first_payment_attempt_network_decline_code: payment_revenue_recovery_metadata + .first_payment_attempt_network_decline_code + .clone(), + first_payment_attempt_pg_error_code: payment_revenue_recovery_metadata + .first_payment_attempt_pg_error_code + .clone(), } }); let apple_pay_details = feature_metadata diff --git a/crates/router/src/core/revenue_recovery.rs b/crates/router/src/core/revenue_recovery.rs index ee22507b8f..235d57f96d 100644 --- a/crates/router/src/core/revenue_recovery.rs +++ b/crates/router/src/core/revenue_recovery.rs @@ -69,14 +69,14 @@ pub async fn perform_execute_payment( match record_attempt { Ok(_) => { - let action = types::Action::execute_payment( + let action = Box::pin(types::Action::execute_payment( state, revenue_recovery_payment_data.merchant_account.get_id(), payment_intent, execute_task_process, revenue_recovery_payment_data, &revenue_recovery_metadata, - ) + )) .await?; Box::pin(action.execute_payment_task_response_handler( state, diff --git a/crates/router/src/core/revenue_recovery/api.rs b/crates/router/src/core/revenue_recovery/api.rs index 6046596485..b58d716683 100644 --- a/crates/router/src/core/revenue_recovery/api.rs +++ b/crates/router/src/core/revenue_recovery/api.rs @@ -183,16 +183,22 @@ pub async fn record_internal_attempt_api( message: "get_revenue_recovery_attempt was not constructed".to_string(), })?; - let request_payload = revenue_recovery_attempt_data.create_payment_record_request( - &revenue_recovery_payment_data.billing_mca.id, - Some( - revenue_recovery_metadata - .active_attempt_payment_connector_id - .clone(), - ), - Some(revenue_recovery_metadata.connector), - common_enums::TriggeredBy::Internal, - ); + let request_payload = revenue_recovery_attempt_data + .create_payment_record_request( + state, + &revenue_recovery_payment_data.billing_mca.id, + Some( + revenue_recovery_metadata + .active_attempt_payment_connector_id + .clone(), + ), + Some(revenue_recovery_metadata.connector), + common_enums::TriggeredBy::Internal, + ) + .await + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "Cannot Create the payment record Request".to_string(), + })?; let merchant_context_from_revenue_recovery_payment_data = MerchantContext::NormalMerchant(Box::new(Context( diff --git a/crates/router/src/core/webhooks/recovery_incoming.rs b/crates/router/src/core/webhooks/recovery_incoming.rs index 87c1a79aa1..18a51c3e9b 100644 --- a/crates/router/src/core/webhooks/recovery_incoming.rs +++ b/crates/router/src/core/webhooks/recovery_incoming.rs @@ -545,14 +545,17 @@ impl RevenueRecoveryAttempt { errors::RevenueRecoveryError, > { let payment_connector_id = payment_connector_account.as_ref().map(|account: &hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount| account.id.clone()); - let request_payload = self.create_payment_record_request( - billing_connector_account_id, - payment_connector_id, - payment_connector_account - .as_ref() - .map(|account| account.connector_name), - common_enums::TriggeredBy::External, - ); + let request_payload = self + .create_payment_record_request( + state, + billing_connector_account_id, + payment_connector_id, + payment_connector_account + .as_ref() + .map(|account| account.connector_name), + common_enums::TriggeredBy::External, + ) + .await?; let attempt_response = Box::pin(payments::record_attempt_core( state.clone(), req_state.clone(), @@ -593,13 +596,15 @@ impl RevenueRecoveryAttempt { Ok(response) } - pub fn create_payment_record_request( + pub async fn create_payment_record_request( &self, + state: &SessionState, billing_merchant_connector_account_id: &id_type::MerchantConnectorAccountId, payment_merchant_connector_account_id: Option, payment_connector: Option, triggered_by: common_enums::TriggeredBy, - ) -> api_payments::PaymentsAttemptRecordRequest { + ) -> CustomResult + { let revenue_recovery_attempt_data = &self.0; let amount_details = api_payments::PaymentAttemptAmountDetails::from(revenue_recovery_attempt_data); @@ -609,9 +614,27 @@ impl RevenueRecoveryAttempt { attempt_triggered_by: triggered_by, }), }; + + let card_info = revenue_recovery_attempt_data + .card_isin + .clone() + .async_and_then(|isin| async move { + let issuer_identifier_number = isin.clone(); + state + .store + .get_card_info(issuer_identifier_number.as_str()) + .await + .map_err(|error| services::logger::warn!(card_info_error=?error)) + .ok() + }) + .await + .flatten(); + + let card_issuer = card_info.and_then(|info| info.card_issuer); + let error = Option::::from(revenue_recovery_attempt_data); - api_payments::PaymentsAttemptRecordRequest { + Ok(api_payments::PaymentsAttemptRecordRequest { amount_details, status: revenue_recovery_attempt_data.status, billing: None, @@ -637,7 +660,9 @@ impl RevenueRecoveryAttempt { retry_count: revenue_recovery_attempt_data.retry_count, invoice_next_billing_time: revenue_recovery_attempt_data.invoice_next_billing_time, triggered_by, - } + card_network: revenue_recovery_attempt_data.card_network.clone(), + card_issuer, + }) } pub async fn find_payment_merchant_connector_account(