mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(stripe): get error message for failed redirect payments (#615)
This commit is contained in:
		| @ -130,7 +130,7 @@ fn get_payer_name(address: &AddressDetails) -> Option<Secret<String>> { | ||||
|         .last_name | ||||
|         .clone() | ||||
|         .map_or("".to_string(), |last_name| last_name.peek().to_string()); | ||||
|     let name: String = format!("{} {}", first_name, last_name).trim().to_string(); | ||||
|     let name: String = format!("{first_name} {last_name}").trim().to_string(); | ||||
|     if !name.is_empty() { | ||||
|         Some(Secret::new(name)) | ||||
|     } else { | ||||
|  | ||||
| @ -266,7 +266,7 @@ impl | ||||
|     { | ||||
|         let response: stripe::PaymentIntentResponse = res | ||||
|             .response | ||||
|             .parse_struct("PaymentIntentResponse") | ||||
|             .parse_struct("PaymentSyncResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
| @ -982,7 +982,12 @@ impl services::ConnectorRedirectResponse for Stripe { | ||||
|         Ok(query | ||||
|             .redirect_status | ||||
|             .map_or(payments::CallConnectorAction::Trigger, |status| { | ||||
|                 payments::CallConnectorAction::StatusUpdate(status.into()) | ||||
|                 // Get failed error message by triggering call to connector | ||||
|                 if status == transformers::StripePaymentStatus::Failed { | ||||
|                     payments::CallConnectorAction::Trigger | ||||
|                 } else { | ||||
|                     payments::CallConnectorAction::StatusUpdate(status.into()) | ||||
|                 } | ||||
|             })) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use std::str::FromStr; | ||||
| use std::{ops::Deref, str::FromStr}; | ||||
|  | ||||
| use api_models::{self, payments}; | ||||
| use common_utils::{fp_utils, pii::Email}; | ||||
| @ -459,7 +459,7 @@ impl From<StripePaymentStatus> for enums::AttemptStatus { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] | ||||
| #[derive(Debug, Default, Eq, PartialEq, Deserialize)] | ||||
| pub struct PaymentIntentResponse { | ||||
|     pub id: String, | ||||
|     pub object: String, | ||||
| @ -477,6 +477,22 @@ pub struct PaymentIntentResponse { | ||||
|     pub metadata: StripeMetadata, | ||||
|     pub next_action: Option<StripeNextActionResponse>, | ||||
|     pub payment_method_options: Option<StripePaymentMethodOptions>, | ||||
|     pub last_payment_error: Option<ErrorDetails>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Eq, PartialEq, Deserialize)] | ||||
| pub struct PaymentSyncResponse { | ||||
|     #[serde(flatten)] | ||||
|     pub intent_fields: PaymentIntentResponse, | ||||
|     pub last_payment_error: Option<ErrorDetails>, | ||||
| } | ||||
|  | ||||
| impl Deref for PaymentSyncResponse { | ||||
|     type Target = PaymentIntentResponse; | ||||
|  | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         &self.intent_fields | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] | ||||
| @ -538,6 +554,63 @@ impl<F, T> | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, T> | ||||
|     TryFrom<types::ResponseRouterData<F, PaymentSyncResponse, T, types::PaymentsResponseData>> | ||||
|     for types::RouterData<F, T, types::PaymentsResponseData> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::ResponseRouterData<F, PaymentSyncResponse, T, types::PaymentsResponseData>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let redirection_data = item.response.next_action.as_ref().map( | ||||
|             |StripeNextActionResponse::RedirectToUrl(response)| { | ||||
|                 services::RedirectForm::from((response.url.clone(), services::Method::Get)) | ||||
|             }, | ||||
|         ); | ||||
|  | ||||
|         let mandate_reference = | ||||
|             item.response | ||||
|                 .payment_method_options | ||||
|                 .to_owned() | ||||
|                 .and_then(|payment_method_options| match payment_method_options { | ||||
|                     StripePaymentMethodOptions::Card { | ||||
|                         mandate_options, .. | ||||
|                     } => mandate_options.map(|mandate_options| mandate_options.reference), | ||||
|                     StripePaymentMethodOptions::Klarna {} | ||||
|                     | StripePaymentMethodOptions::Affirm {} | ||||
|                     | StripePaymentMethodOptions::AfterpayClearpay {} => None, | ||||
|                 }); | ||||
|  | ||||
|         let error_res = | ||||
|             item.response | ||||
|                 .last_payment_error | ||||
|                 .as_ref() | ||||
|                 .map(|error| types::ErrorResponse { | ||||
|                     code: error.code.to_owned().unwrap_or_default(), | ||||
|                     message: error.message.to_owned().unwrap_or_default(), | ||||
|                     reason: None, | ||||
|                     status_code: item.http_code, | ||||
|                 }); | ||||
|  | ||||
|         let response = error_res.map_or( | ||||
|             Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), | ||||
|                 redirection_data, | ||||
|                 mandate_reference, | ||||
|                 connector_metadata: None, | ||||
|             }), | ||||
|             Err, | ||||
|         ); | ||||
|  | ||||
|         Ok(Self { | ||||
|             status: enums::AttemptStatus::from(item.response.status.to_owned()), | ||||
|             response, | ||||
|             amount_captured: Some(item.response.amount_received), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, T> | ||||
|     TryFrom<types::ResponseRouterData<F, SetupIntentResponse, T, types::PaymentsResponseData>> | ||||
|     for types::RouterData<F, T, types::PaymentsResponseData> | ||||
|  | ||||
| @ -273,7 +273,7 @@ async fn payment_response_update_tracker<F: Clone, T>( | ||||
|         Err(err) => ( | ||||
|             Some(storage::PaymentAttemptUpdate::ErrorUpdate { | ||||
|                 connector: Some(router_data.connector.clone()), | ||||
|                 status: storage::enums::AttemptStatus::Failure, | ||||
|                 status: router_data.status.foreign_into(), | ||||
|                 error_message: Some(err.message), | ||||
|                 error_code: Some(err.code), | ||||
|             }), | ||||
| @ -364,7 +364,7 @@ async fn payment_response_update_tracker<F: Clone, T>( | ||||
|  | ||||
|     let payment_intent_update = match router_data.response { | ||||
|         Err(_) => storage::PaymentIntentUpdate::PGStatusUpdate { | ||||
|             status: enums::IntentStatus::Failed, | ||||
|             status: router_data.status.foreign_into(), | ||||
|         }, | ||||
|         Ok(_) => storage::PaymentIntentUpdate::ResponseUpdate { | ||||
|             status: router_data.status.foreign_into(), | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Narayan Bhat
					Narayan Bhat