mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-30 17:47:54 +08:00 
			
		
		
		
	feat: Session flow for Apple Pay trustpay (#1155)
This commit is contained in:
		 Sangamesh Kulkarni
					Sangamesh Kulkarni
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							d21fcc7bfc
						
					
				
				
					commit
					a6e91a828b
				
			| @ -257,3 +257,6 @@ refund_duration = 1000 | ||||
| refund_tolerance = 100 | ||||
| refund_retrieve_duration = 500 | ||||
| refund_retrieve_tolerance = 100 | ||||
|  | ||||
| [delayed_session_response] | ||||
| connectors_with_delayed_session_response = "trustpay" | ||||
|  | ||||
| @ -1620,6 +1620,8 @@ pub struct PaymentsSessionRequest { | ||||
|     /// Merchant connector details used to make payments. | ||||
|     #[schema(value_type = Option<MerchantConnectorDetailsWrap>)] | ||||
|     pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>, | ||||
|     /// Identifier for the delayed session response | ||||
|     pub delayed_session_token: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] | ||||
| @ -1783,13 +1785,47 @@ pub struct ApplepaySessionTokenResponse { | ||||
|     /// Session object for Apple Pay | ||||
|     pub session_token_data: ApplePaySessionResponse, | ||||
|     /// Payment request object for Apple Pay | ||||
|     pub payment_request_data: ApplePayPaymentRequest, | ||||
|     pub payment_request_data: Option<ApplePayPaymentRequest>, | ||||
|     /// The session token is w.r.t this connector | ||||
|     pub connector: String, | ||||
|     /// Identifier for the delayed session response | ||||
|     pub delayed_session_token: bool, | ||||
|     /// The next action for the sdk (ex: calling confirm or sync call) | ||||
|     pub sdk_next_action: SdkNextAction, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, Clone, ToSchema)] | ||||
| pub struct SdkNextAction { | ||||
|     /// The type of next action | ||||
|     pub next_action: NextActionCall, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, Clone, ToSchema)] | ||||
| #[serde(rename_all = "snake_case")] | ||||
| pub enum NextActionCall { | ||||
|     /// The next action call is confirm | ||||
|     Confirm, | ||||
|     /// The next action call is sync | ||||
|     Sync, | ||||
|     /// The next action call is session | ||||
|     SessionToken, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema)] | ||||
| #[serde(untagged)] | ||||
| pub enum ApplePaySessionResponse { | ||||
|     ///  We get this session response, when third party sdk is involved | ||||
|     ThirdPartySdk(ThirdPartySdkSessionResponse), | ||||
|     ///  We get this session response, when there is no involvement of third party sdk | ||||
|     /// This is the common response most of the times | ||||
|     NoThirdPartySdk(Option<NoThirdPartySdkSessionResponse>), | ||||
|     /// This is for the empty session response | ||||
|     NoSessionResponse, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] | ||||
| #[serde(rename_all(deserialize = "camelCase"))] | ||||
| pub struct ApplePaySessionResponse { | ||||
| pub struct NoThirdPartySdkSessionResponse { | ||||
|     /// Timestamp at which session is requested | ||||
|     pub epoch_timestamp: u64, | ||||
|     /// Timestamp at which session expires | ||||
| @ -1814,6 +1850,21 @@ pub struct ApplePaySessionResponse { | ||||
|     pub psp_id: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema)] | ||||
| pub struct ThirdPartySdkSessionResponse { | ||||
|     pub secrets: SecretInfoToInitiateSdk, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] | ||||
| pub struct SecretInfoToInitiateSdk { | ||||
|     // Authorization secrets used by client to initiate sdk | ||||
|     #[schema(value_type = String)] | ||||
|     pub display: Secret<String>, | ||||
|     // Authorization secrets used by client for payment | ||||
|     #[schema(value_type = String)] | ||||
|     pub payment: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] | ||||
| pub struct ApplePayPaymentRequest { | ||||
|     /// The code for country | ||||
| @ -1827,7 +1878,7 @@ pub struct ApplePayPaymentRequest { | ||||
|     pub merchant_capabilities: Vec<String>, | ||||
|     /// The list of supported networks | ||||
|     pub supported_networks: Vec<String>, | ||||
|     pub merchant_identifier: String, | ||||
|     pub merchant_identifier: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] | ||||
| @ -1836,7 +1887,7 @@ pub struct AmountInfo { | ||||
|     pub label: String, | ||||
|     /// A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending. | ||||
|     #[serde(rename = "type")] | ||||
|     pub total_type: String, | ||||
|     pub total_type: Option<String>, | ||||
|     /// The total amount for the payment | ||||
|     pub amount: String, | ||||
| } | ||||
|  | ||||
| @ -83,6 +83,7 @@ pub struct Settings { | ||||
|     pub dummy_connector: DummyConnector, | ||||
|     #[cfg(feature = "email")] | ||||
|     pub email: EmailSettings, | ||||
|     pub delayed_session_response: DelayedSessionConfig, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| @ -506,6 +507,27 @@ pub struct FileUploadConfig { | ||||
|     pub bucket_name: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct DelayedSessionConfig { | ||||
|     #[serde(deserialize_with = "delayed_session_deser")] | ||||
|     pub connectors_with_delayed_session_response: HashSet<api_models::enums::Connector>, | ||||
| } | ||||
|  | ||||
| fn delayed_session_deser<'a, D>( | ||||
|     deserializer: D, | ||||
| ) -> Result<HashSet<api_models::enums::Connector>, D::Error> | ||||
| where | ||||
|     D: Deserializer<'a>, | ||||
| { | ||||
|     let value = <String>::deserialize(deserializer)?; | ||||
|     value | ||||
|         .trim() | ||||
|         .split(',') | ||||
|         .map(api_models::enums::Connector::from_str) | ||||
|         .collect::<Result<_, _>>() | ||||
|         .map_err(D::Error::custom) | ||||
| } | ||||
|  | ||||
| impl Settings { | ||||
|     pub fn new() -> ApplicationResult<Self> { | ||||
|         Self::with_config_path(None) | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use api_models::enums as api_enums; | ||||
| use api_models::{enums as api_enums, payments}; | ||||
| use base64::Engine; | ||||
| use common_utils::{ | ||||
|     ext_traits::{ByteSliceExt, StringExt, ValueExt}, | ||||
| @ -286,10 +286,11 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons | ||||
|         let wallet_token = consts::BASE64_ENGINE | ||||
|             .decode(response.wallet_token.clone().expose()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ConnectorError::ParsingFailed)?; | ||||
|             .change_context(errors::ConnectorError::ResponseHandlingFailed)?; | ||||
|  | ||||
|         let session_response: api_models::payments::ApplePaySessionResponse = wallet_token[..] | ||||
|             .parse_struct("ApplePayResponse") | ||||
|         let session_response: api_models::payments::NoThirdPartySdkSessionResponse = | ||||
|             wallet_token[..] | ||||
|                 .parse_struct("NoThirdPartySdkSessionResponse") | ||||
|                 .change_context(errors::ConnectorError::ParsingFailed)?; | ||||
|  | ||||
|         let metadata = item.data.get_connector_meta()?.expose(); | ||||
| @ -303,13 +304,16 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons | ||||
|             response: Ok(types::PaymentsResponseData::SessionResponse { | ||||
|                 session_token: types::api::SessionToken::ApplePay(Box::new( | ||||
|                     api_models::payments::ApplepaySessionTokenResponse { | ||||
|                         session_token_data: session_response, | ||||
|                         payment_request_data: api_models::payments::ApplePayPaymentRequest { | ||||
|                         session_token_data: | ||||
|                             api_models::payments::ApplePaySessionResponse::NoThirdPartySdk(Some( | ||||
|                                 session_response, | ||||
|                             )), | ||||
|                         payment_request_data: Some(api_models::payments::ApplePayPaymentRequest { | ||||
|                             country_code: item.data.get_billing_country()?, | ||||
|                             currency_code: item.data.request.currency.to_string(), | ||||
|                             total: api_models::payments::AmountInfo { | ||||
|                                 label: applepay_metadata.data.payment_request_data.label, | ||||
|                                 total_type: "final".to_string(), | ||||
|                                 total_type: Some("final".to_string()), | ||||
|                                 amount: item.data.request.amount.to_string(), | ||||
|                             }, | ||||
|                             merchant_capabilities: applepay_metadata | ||||
| @ -320,14 +324,23 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons | ||||
|                                 .data | ||||
|                                 .payment_request_data | ||||
|                                 .supported_networks, | ||||
|                             merchant_identifier: applepay_metadata | ||||
|                             merchant_identifier: Some( | ||||
|                                 applepay_metadata | ||||
|                                     .data | ||||
|                                     .session_token_data | ||||
|                                     .merchant_identifier, | ||||
|                         }, | ||||
|                             ), | ||||
|                         }), | ||||
|                         connector: "bluesnap".to_string(), | ||||
|                         delayed_session_token: false, | ||||
|                         sdk_next_action: { | ||||
|                             payments::SdkNextAction { | ||||
|                                 next_action: payments::NextActionCall::Confirm, | ||||
|                             } | ||||
|                         }, | ||||
|                     }, | ||||
|                 )), | ||||
|                 response_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|  | ||||
| @ -249,6 +249,7 @@ impl<F, T> | ||||
|                         session_token: item.response.client_token.value, | ||||
|                     }, | ||||
|                 )), | ||||
|                 response_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|  | ||||
| @ -78,6 +78,7 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>> | ||||
|                         session_id: response.session_id.clone(), | ||||
|                     }, | ||||
|                 )), | ||||
|                 response_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|  | ||||
| @ -339,6 +339,87 @@ impl api::PaymentSession for Trustpay {} | ||||
| impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData> | ||||
|     for Trustpay | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsSessionRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { | ||||
|         let mut header = vec![( | ||||
|             headers::CONTENT_TYPE.to_string(), | ||||
|             types::PaymentsSessionType::get_content_type(self) | ||||
|                 .to_string() | ||||
|                 .into(), | ||||
|         )]; | ||||
|         let mut api_key = self.get_auth_header(&req.connector_auth_type)?; | ||||
|         header.append(&mut api_key); | ||||
|         Ok(header) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsSessionRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Ok(format!("{}{}", self.base_url(connectors), "api/v1/intent")) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::PaymentsSessionRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let create_intent_req = trustpay::TrustpayCreateIntentRequest::try_from(req)?; | ||||
|         let trustpay_req = | ||||
|             utils::Encode::<trustpay::TrustpayCreateIntentRequest>::url_encode(&create_intent_req) | ||||
|                 .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(trustpay_req)) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::PaymentsSessionRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let req = Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::PaymentsSessionType::get_headers( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
|                 .url(&types::PaymentsSessionType::get_url(self, req, connectors)?) | ||||
|                 .body(types::PaymentsSessionType::get_request_body(self, req)?) | ||||
|                 .build(), | ||||
|         ); | ||||
|         Ok(req) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsSessionRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> { | ||||
|         let response: trustpay::TrustpayCreateIntentResponse = res | ||||
|             .response | ||||
|             .parse_struct("TrustpayCreateIntentResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl api::PaymentAuthorize for Trustpay {} | ||||
|  | ||||
| @ -801,6 +801,128 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, TrustpayAuthUpdateResponse, T, t | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct TrustpayCreateIntentRequest { | ||||
|     pub amount: String, | ||||
|     pub currency: String, | ||||
|     // If true, Apple Pay will be initialized | ||||
|     pub init_apple_pay: Option<bool>, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsSessionRouterData> for TrustpayCreateIntentRequest { | ||||
|     type Error = Error; | ||||
|     fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             amount: item.request.amount.to_string(), | ||||
|             currency: item.request.currency.to_string(), | ||||
|             init_apple_pay: Some(true), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct TrustpayCreateIntentResponse { | ||||
|     // TrustPay's authorization secrets used by client | ||||
|     pub secrets: SdkSecretInfo, | ||||
|     // 	Data object to be used for Apple Pay | ||||
|     pub apple_init_result_data: TrustpayApplePayResponse, | ||||
|     // Unique operation/transaction identifier | ||||
|     pub instance_id: String, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct SdkSecretInfo { | ||||
|     pub display: Secret<String>, | ||||
|     pub payment: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct TrustpayApplePayResponse { | ||||
|     pub country_code: api_models::enums::CountryAlpha2, | ||||
|     pub currency_code: String, | ||||
|     pub supported_networks: Vec<String>, | ||||
|     pub merchant_capabilities: Vec<String>, | ||||
|     pub total: ApplePayTotalInfo, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ApplePayTotalInfo { | ||||
|     pub label: String, | ||||
|     pub amount: String, | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::PaymentsSessionResponseRouterData<TrustpayCreateIntentResponse>> | ||||
|     for types::PaymentsSessionRouterData | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::PaymentsSessionResponseRouterData<TrustpayCreateIntentResponse>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let response = item.response; | ||||
|         Ok(Self { | ||||
|             response: Ok(types::PaymentsResponseData::SessionResponse { | ||||
|                 session_token: types::api::SessionToken::ApplePay(Box::new( | ||||
|                     api_models::payments::ApplepaySessionTokenResponse { | ||||
|                         session_token_data: | ||||
|                             api_models::payments::ApplePaySessionResponse::ThirdPartySdk( | ||||
|                                 api_models::payments::ThirdPartySdkSessionResponse { | ||||
|                                     secrets: response.secrets.into(), | ||||
|                                 }, | ||||
|                             ), | ||||
|                         payment_request_data: Some(api_models::payments::ApplePayPaymentRequest { | ||||
|                             country_code: response.apple_init_result_data.country_code, | ||||
|                             currency_code: response.apple_init_result_data.currency_code.clone(), | ||||
|                             supported_networks: response | ||||
|                                 .apple_init_result_data | ||||
|                                 .supported_networks | ||||
|                                 .clone(), | ||||
|                             merchant_capabilities: response | ||||
|                                 .apple_init_result_data | ||||
|                                 .merchant_capabilities | ||||
|                                 .clone(), | ||||
|                             total: response.apple_init_result_data.total.into(), | ||||
|                             merchant_identifier: None, | ||||
|                         }), | ||||
|                         connector: "trustpay".to_string(), | ||||
|                         delayed_session_token: true, | ||||
|                         sdk_next_action: { | ||||
|                             api_models::payments::SdkNextAction { | ||||
|                                 next_action: api_models::payments::NextActionCall::Sync, | ||||
|                             } | ||||
|                         }, | ||||
|                     }, | ||||
|                 )), | ||||
|                 response_id: Some(response.instance_id), | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SdkSecretInfo> for api_models::payments::SecretInfoToInitiateSdk { | ||||
|     fn from(value: SdkSecretInfo) -> Self { | ||||
|         Self { | ||||
|             display: value.display, | ||||
|             payment: value.payment, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<ApplePayTotalInfo> for api_models::payments::AmountInfo { | ||||
|     fn from(value: ApplePayTotalInfo) -> Self { | ||||
|         Self { | ||||
|             label: value.label, | ||||
|             amount: value.amount, | ||||
|             total_type: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct TrustpayRefundRequestCards { | ||||
|  | ||||
| @ -8,10 +8,10 @@ pub mod transformers; | ||||
|  | ||||
| use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; | ||||
|  | ||||
| use actix_web::ResponseError; | ||||
| use api_models::payments::Metadata; | ||||
| use common_utils::pii::Email; | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| use futures::future::join_all; | ||||
| use masking::Secret; | ||||
| use router_env::{instrument, tracing}; | ||||
| use storage_models::ephemeral_key; | ||||
| @ -29,11 +29,10 @@ use self::{ | ||||
| use crate::{ | ||||
|     configs::settings::PaymentMethodTypeTokenFilter, | ||||
|     core::{ | ||||
|         errors::{self, CustomResult, RouterResponse, RouterResult}, | ||||
|         errors::{self, CustomResult, RouterResponse, RouterResult, StorageErrorExt}, | ||||
|         payment_methods::vault, | ||||
|     }, | ||||
|     db::StorageInterface, | ||||
|     logger, | ||||
|     routes::{metrics, AppState}, | ||||
|     scheduler::utils as pt_utils, | ||||
|     services::{self, api::Authenticate}, | ||||
| @ -177,7 +176,7 @@ where | ||||
|             } | ||||
|  | ||||
|             api::ConnectorCallType::Multiple(connectors) => { | ||||
|                 call_multiple_connectors_service( | ||||
|                 get_session_tokens_and_persist_if_required( | ||||
|                     state, | ||||
|                     &merchant_account, | ||||
|                     connectors, | ||||
| @ -564,7 +563,7 @@ where | ||||
|     router_data_res | ||||
| } | ||||
|  | ||||
| pub async fn call_multiple_connectors_service<F, Op, Req>( | ||||
| pub async fn get_session_tokens_and_persist_if_required<F, Op, Req>( | ||||
|     state: &AppState, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|     connectors: Vec<api::SessionConnectorData>, | ||||
| @ -596,38 +595,54 @@ where | ||||
|             .construct_router_data(state, connector_id, merchant_account, customer) | ||||
|             .await?; | ||||
|  | ||||
|         let res = router_data.decide_flows( | ||||
|         let res = router_data | ||||
|             .decide_flows( | ||||
|                 state, | ||||
|                 &session_connector_data.connector, | ||||
|                 customer, | ||||
|                 CallConnectorAction::Trigger, | ||||
|                 merchant_account, | ||||
|         ); | ||||
|             ) | ||||
|             .await; | ||||
|  | ||||
|         join_handlers.push(res); | ||||
|         let router_res = match res { | ||||
|             Ok(router_result) => Ok(router_result), | ||||
|             Err(error) => { | ||||
|                 let err = error.current_context(); | ||||
|                 Err(errors::ApiErrorResponse::ExternalConnectorError { | ||||
|                     code: err.error_code(), | ||||
|                     message: err.error_message(), | ||||
|                     connector: connector_id.to_string(), | ||||
|                     status_code: err.status_code().into(), | ||||
|                     reason: None, | ||||
|                 }) | ||||
|             } | ||||
|         }?; | ||||
|  | ||||
|         join_handlers.push(router_res.response.clone()); | ||||
|  | ||||
|         if connector_id == "trustpay" && payment_data.delayed_session_token.unwrap_or(false) { | ||||
|             //Fix: Add post update tracker for payment_session operation | ||||
|             let response_id = router_res.response.ok().and_then(|res| match res { | ||||
|                 types::PaymentsResponseData::SessionResponse { response_id, .. } => response_id, | ||||
|                 _ => None, | ||||
|             }); | ||||
|  | ||||
|             update_connector_txn_id_in_payment_attempt( | ||||
|                 state, | ||||
|                 merchant_account, | ||||
|                 &payment_data, | ||||
|                 response_id, | ||||
|             ) | ||||
|             .await?; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     let result = join_all(join_handlers).await; | ||||
|  | ||||
|     for (connector_res, session_connector) in result.into_iter().zip(connectors) { | ||||
|         let connector_name = session_connector.connector.connector_name.to_string(); | ||||
|         match connector_res { | ||||
|             Ok(connector_response) => { | ||||
|                 if let Ok(types::PaymentsResponseData::SessionResponse { session_token }) = | ||||
|                     connector_response.response | ||||
|                 { | ||||
|     for connector_res in join_handlers.into_iter().flatten() { | ||||
|         if let types::PaymentsResponseData::SessionResponse { session_token, .. } = connector_res { | ||||
|             payment_data.sessions_token.push(session_token); | ||||
|         } | ||||
|     } | ||||
|             Err(connector_error) => { | ||||
|                 logger::error!( | ||||
|                     "sessions_connector_error {} {:?}", | ||||
|                     connector_name, | ||||
|                     connector_error | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let call_connectors_end_time = Instant::now(); | ||||
|     let call_connectors_duration = | ||||
| @ -637,6 +652,31 @@ where | ||||
|     Ok(payment_data) | ||||
| } | ||||
|  | ||||
| pub async fn update_connector_txn_id_in_payment_attempt<F>( | ||||
|     state: &AppState, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|     payment_data: &PaymentData<F>, | ||||
|     response_id: Option<String>, | ||||
| ) -> RouterResult<()> | ||||
| where | ||||
|     F: Clone, | ||||
| { | ||||
|     let payment_attempt_update = storage::PaymentAttemptUpdate::SessionUpdate { | ||||
|         connector_transaction_id: response_id, | ||||
|     }; | ||||
|  | ||||
|     let db = &*state.store; | ||||
|     db.update_payment_attempt_with_attempt_id( | ||||
|         payment_data.payment_attempt.to_owned(), | ||||
|         payment_attempt_update, | ||||
|         merchant_account.storage_scheme, | ||||
|     ) | ||||
|     .await | ||||
|     .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub async fn call_create_connector_customer_if_required<F, Req>( | ||||
|     state: &AppState, | ||||
|     customer: &Option<domain::Customer>, | ||||
| @ -975,6 +1015,7 @@ where | ||||
|     pub connector_customer_id: Option<String>, | ||||
|     pub ephemeral_key: Option<ephemeral_key::EphemeralKey>, | ||||
|     pub redirect_response: Option<api_models::payments::RedirectResponse>, | ||||
|     pub delayed_session_token: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| @ -1085,7 +1126,7 @@ pub async fn list_payments( | ||||
| ) -> RouterResponse<api::PaymentListResponse> { | ||||
|     use futures::stream::StreamExt; | ||||
|  | ||||
|     use crate::{core::errors::utils::StorageErrorExt, types::transformers::ForeignFrom}; | ||||
|     use crate::types::transformers::ForeignFrom; | ||||
|  | ||||
|     helpers::validate_payment_list_request(&constraints)?; | ||||
|     let merchant_id = &merchant.merchant_id; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| use api_models::payments as payment_types; | ||||
| use async_trait::async_trait; | ||||
| use common_utils::ext_traits::ByteSliceExt; | ||||
| use error_stack::{report, ResultExt}; | ||||
| use error_stack::{Report, ResultExt}; | ||||
|  | ||||
| use super::{ConstructFlowSpecificData, Feature}; | ||||
| use crate::{ | ||||
| @ -10,7 +10,7 @@ use crate::{ | ||||
|         errors::{self, ConnectorErrorExt, RouterResult}, | ||||
|         payments::{self, access_token, transformers, PaymentData}, | ||||
|     }, | ||||
|     headers, | ||||
|     headers, logger, | ||||
|     routes::{self, metrics}, | ||||
|     services, | ||||
|     types::{self, api, domain}, | ||||
| @ -78,18 +78,22 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn mk_applepay_session_request( | ||||
|     state: &routes::AppState, | ||||
|     router_data: &types::PaymentsSessionRouterData, | ||||
| ) -> RouterResult<(services::Request, payment_types::ApplepaySessionTokenData)> { | ||||
|     let connector_metadata = router_data.connector_meta_data.clone(); | ||||
|  | ||||
|     let applepay_metadata = connector_metadata | ||||
| fn get_applepay_metadata( | ||||
|     connector_metadata: Option<common_utils::pii::SecretSerdeValue>, | ||||
| ) -> RouterResult<payment_types::ApplepaySessionTokenData> { | ||||
|     connector_metadata | ||||
|         .parse_value::<payment_types::ApplepaySessionTokenData>("ApplepaySessionTokenData") | ||||
|         .change_context(errors::ApiErrorResponse::InvalidDataFormat { | ||||
|             field_name: "connector_metadata".to_string(), | ||||
|             expected_format: "applepay_metadata_format".to_string(), | ||||
|         })?; | ||||
|         }) | ||||
| } | ||||
|  | ||||
| fn mk_applepay_session_request( | ||||
|     state: &routes::AppState, | ||||
|     router_data: &types::PaymentsSessionRouterData, | ||||
| ) -> RouterResult<services::Request> { | ||||
|     let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; | ||||
|     let request = payment_types::ApplepaySessionRequest { | ||||
|         merchant_identifier: applepay_metadata | ||||
|             .data | ||||
| @ -134,14 +138,10 @@ fn mk_applepay_session_request( | ||||
|                 .clone(), | ||||
|         )) | ||||
|         .add_certificate_key(Some( | ||||
|             applepay_metadata | ||||
|                 .data | ||||
|                 .session_token_data | ||||
|                 .certificate_keys | ||||
|                 .clone(), | ||||
|             applepay_metadata.data.session_token_data.certificate_keys, | ||||
|         )) | ||||
|         .build(); | ||||
|     Ok((session_request, applepay_metadata)) | ||||
|     Ok(session_request) | ||||
| } | ||||
|  | ||||
| async fn create_applepay_session_token( | ||||
| @ -149,36 +149,17 @@ async fn create_applepay_session_token( | ||||
|     router_data: &types::PaymentsSessionRouterData, | ||||
|     connector: &api::ConnectorData, | ||||
| ) -> RouterResult<types::PaymentsSessionRouterData> { | ||||
|     let (applepay_session_request, applepay_metadata) = | ||||
|         mk_applepay_session_request(state, router_data)?; | ||||
|     let response = services::call_connector_api(state, applepay_session_request) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failure in calling connector api")?; | ||||
|     let session_response: payment_types::ApplePaySessionResponse = match response { | ||||
|         Ok(resp) => resp | ||||
|             .response | ||||
|             .parse_struct("ApplePaySessionResponse") | ||||
|             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|             .attach_printable("Failed to parse ApplePaySessionResponse struct"), | ||||
|         Err(err) => { | ||||
|             let error_response: payment_types::ApplepayErrorResponse = err | ||||
|                 .response | ||||
|                 .parse_struct("ApplepayErrorResponse") | ||||
|                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                 .attach_printable("Failed to parse ApplepayErrorResponse struct")?; | ||||
|             Err( | ||||
|                 report!(errors::ApiErrorResponse::InternalServerError).attach_printable(format!( | ||||
|                     "Failed with {} status code and the error response is {:?}", | ||||
|                     err.status_code, error_response | ||||
|                 )), | ||||
|             ) | ||||
|         } | ||||
|     }?; | ||||
|     let connectors_with_delayed_response = &state | ||||
|         .conf | ||||
|         .delayed_session_response | ||||
|         .connectors_with_delayed_session_response; | ||||
|  | ||||
|     let connector_name = connector.connector_name; | ||||
|     let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; | ||||
|  | ||||
|     let amount_info = payment_types::AmountInfo { | ||||
|         label: applepay_metadata.data.payment_request_data.label, | ||||
|         total_type: "final".to_string(), | ||||
|         total_type: Some("final".to_string()), | ||||
|         amount: connector::utils::to_currency_base_unit( | ||||
|             router_data.request.amount, | ||||
|             router_data.request.currency, | ||||
| @ -206,26 +187,84 @@ async fn create_applepay_session_token( | ||||
|             .data | ||||
|             .payment_request_data | ||||
|             .supported_networks, | ||||
|         merchant_identifier: applepay_metadata | ||||
|         merchant_identifier: Some( | ||||
|             applepay_metadata | ||||
|                 .data | ||||
|                 .session_token_data | ||||
|                 .merchant_identifier, | ||||
|         ), | ||||
|     }; | ||||
|  | ||||
|     let response_router_data = types::PaymentsSessionRouterData { | ||||
|     let delayed_response = connectors_with_delayed_response.contains(&connector_name); | ||||
|  | ||||
|     if delayed_response { | ||||
|         let delayed_response_apple_pay_session = | ||||
|             payment_types::ApplePaySessionResponse::NoSessionResponse; | ||||
|         create_apple_pay_session_response( | ||||
|             router_data, | ||||
|             delayed_response_apple_pay_session, | ||||
|             None, // Apple pay payment request will be none for delayed session response | ||||
|             connector_name.to_string(), | ||||
|             delayed_response, | ||||
|             payment_types::NextActionCall::SessionToken, | ||||
|             None, //Response Id will be none for delayed session response | ||||
|         ) | ||||
|     } else { | ||||
|         let applepay_session_request = mk_applepay_session_request(state, router_data)?; | ||||
|         let response = services::call_connector_api(state, applepay_session_request).await; | ||||
|         log_session_response_if_error(&response); | ||||
|  | ||||
|         let session_response = response | ||||
|             .ok() | ||||
|             .and_then(|apple_pay_res| { | ||||
|                 apple_pay_res | ||||
|                     .map(|res| { | ||||
|                         let response: Result< | ||||
|                             payment_types::NoThirdPartySdkSessionResponse, | ||||
|                             Report<common_utils::errors::ParsingError>, | ||||
|                         > = res.response.parse_struct("NoThirdPartySdkSessionResponse"); | ||||
|                         response.ok() | ||||
|                     }) | ||||
|                     .ok() | ||||
|             }) | ||||
|             .flatten(); | ||||
|  | ||||
|         create_apple_pay_session_response( | ||||
|             router_data, | ||||
|             payment_types::ApplePaySessionResponse::NoThirdPartySdk(session_response), | ||||
|             Some(applepay_payment_request), | ||||
|             connector_name.to_string(), | ||||
|             delayed_response, | ||||
|             payment_types::NextActionCall::Confirm, | ||||
|             None, // Response Id will be none for No third party sdk response | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn create_apple_pay_session_response( | ||||
|     router_data: &types::PaymentsSessionRouterData, | ||||
|     session_response: payment_types::ApplePaySessionResponse, | ||||
|     apple_pay_payment_request: Option<payment_types::ApplePayPaymentRequest>, | ||||
|     connector_name: String, | ||||
|     delayed_response: bool, | ||||
|     next_action: payment_types::NextActionCall, | ||||
|     response_id: Option<String>, | ||||
| ) -> RouterResult<types::PaymentsSessionRouterData> { | ||||
|     Ok(types::PaymentsSessionRouterData { | ||||
|         response: Ok(types::PaymentsResponseData::SessionResponse { | ||||
|             session_token: payment_types::SessionToken::ApplePay(Box::new( | ||||
|                 payment_types::ApplepaySessionTokenResponse { | ||||
|                     session_token_data: session_response, | ||||
|                     payment_request_data: applepay_payment_request, | ||||
|                     connector: connector.connector_name.to_string(), | ||||
|                     payment_request_data: apple_pay_payment_request, | ||||
|                     connector: connector_name, | ||||
|                     delayed_session_token: delayed_response, | ||||
|                     sdk_next_action: { payment_types::SdkNextAction { next_action } }, | ||||
|                 }, | ||||
|             )), | ||||
|             response_id, | ||||
|         }), | ||||
|         ..router_data.clone() | ||||
|     }; | ||||
|  | ||||
|     Ok(response_router_data) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| fn create_gpay_session_token( | ||||
| @ -271,6 +310,7 @@ fn create_gpay_session_token( | ||||
|                     connector: connector.connector_name.to_string(), | ||||
|                 }, | ||||
|             )), | ||||
|             response_id: None, | ||||
|         }), | ||||
|         ..router_data.clone() | ||||
|     }; | ||||
| @ -278,6 +318,18 @@ fn create_gpay_session_token( | ||||
|     Ok(response_router_data) | ||||
| } | ||||
|  | ||||
| fn log_session_response_if_error( | ||||
|     response: &Result<Result<types::Response, types::Response>, Report<errors::ApiClientError>>, | ||||
| ) { | ||||
|     if let Err(error) = response.as_ref() { | ||||
|         logger::error!(?error); | ||||
|     }; | ||||
|     response | ||||
|         .as_ref() | ||||
|         .ok() | ||||
|         .map(|res| res.as_ref().map_err(|error| logger::error!(?error))); | ||||
| } | ||||
|  | ||||
| impl types::PaymentsSessionRouterData { | ||||
|     pub async fn decide_flow<'a, 'b>( | ||||
|         &'b self, | ||||
|  | ||||
| @ -153,6 +153,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest> | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             None, | ||||
|         )) | ||||
|  | ||||
| @ -159,6 +159,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             None, | ||||
|         )) | ||||
|  | ||||
| @ -204,6 +204,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(CustomerDetails { | ||||
|                 customer_id: request.customer_id.clone(), | ||||
|  | ||||
| @ -247,6 +247,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(CustomerDetails { | ||||
|                 customer_id: request.customer_id.clone(), | ||||
|  | ||||
| @ -252,6 +252,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(CustomerDetails { | ||||
|                 customer_id: request.customer_id.clone(), | ||||
|  | ||||
| @ -182,6 +182,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(payments::CustomerDetails { | ||||
|                 customer_id: request.customer_id.clone(), | ||||
|  | ||||
| @ -172,6 +172,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest> | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: request.delayed_session_token, | ||||
|             }, | ||||
|             Some(customer_details), | ||||
|         )) | ||||
| @ -376,15 +377,15 @@ where | ||||
|         for (connector, payment_method_type, business_sub_label) in | ||||
|             connector_and_supporting_payment_method_type | ||||
|         { | ||||
|             if let Ok(connector_data) = api::ConnectorData::get_connector_by_name( | ||||
|                 connectors, | ||||
|                 &connector, | ||||
|                 api::GetToken::from(payment_method_type), | ||||
|             ) | ||||
|             let connector_type = | ||||
|                 get_connector_type_for_session_token(payment_method_type, request, &connector); | ||||
|             if let Ok(connector_data) = | ||||
|                 api::ConnectorData::get_connector_by_name(connectors, &connector, connector_type) | ||||
|                     .map_err(|err| { | ||||
|                         logger::error!(session_token_error=?err); | ||||
|                         err | ||||
|             }) { | ||||
|                     }) | ||||
|             { | ||||
|                 session_connector_data.push(api::SessionConnectorData { | ||||
|                     payment_method_type, | ||||
|                     connector: connector_data, | ||||
| @ -411,11 +412,11 @@ impl From<api_models::enums::PaymentMethodType> for api::GetToken { | ||||
|  | ||||
| pub fn get_connector_type_for_session_token( | ||||
|     payment_method_type: api_models::enums::PaymentMethodType, | ||||
|     _request: &api::PaymentsSessionRequest, | ||||
|     connector: String, | ||||
|     request: &api::PaymentsSessionRequest, | ||||
|     connector: &str, | ||||
| ) -> api::GetToken { | ||||
|     if payment_method_type == api_models::enums::PaymentMethodType::ApplePay { | ||||
|         if connector == *"bluesnap" { | ||||
|         if is_apple_pay_get_token_connector(connector, request) { | ||||
|             api::GetToken::Connector | ||||
|         } else { | ||||
|             api::GetToken::ApplePayMetadata | ||||
| @ -424,3 +425,17 @@ pub fn get_connector_type_for_session_token( | ||||
|         api::GetToken::from(payment_method_type) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn is_apple_pay_get_token_connector( | ||||
|     connector: &str, | ||||
|     request: &api::PaymentsSessionRequest, | ||||
| ) -> bool { | ||||
|     match connector { | ||||
|         "bluesnap" => true, | ||||
|         "trustpay" => request | ||||
|             .delayed_session_token | ||||
|             .and_then(|delay| delay.then_some(true)) | ||||
|             .is_some(), | ||||
|         _ => false, | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -144,6 +144,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(customer_details), | ||||
|         )) | ||||
|  | ||||
| @ -291,6 +291,7 @@ async fn get_tracker_for_sync< | ||||
|             connector_customer_id: None, | ||||
|             ephemeral_key: None, | ||||
|             redirect_response: None, | ||||
|             delayed_session_token: None, | ||||
|         }, | ||||
|         None, | ||||
|     )) | ||||
|  | ||||
| @ -306,6 +306,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa | ||||
|                 connector_customer_id: None, | ||||
|                 ephemeral_key: None, | ||||
|                 redirect_response: None, | ||||
|                 delayed_session_token: None, | ||||
|             }, | ||||
|             Some(CustomerDetails { | ||||
|                 customer_id: request.customer_id.clone(), | ||||
|  | ||||
| @ -200,6 +200,9 @@ Never share your secret api keys. Keep them guarded and secure. | ||||
|         api_models::payments::PaymentsSessionResponse, | ||||
|         api_models::payments::SessionToken, | ||||
|         api_models::payments::ApplePaySessionResponse, | ||||
|         api_models::payments::ThirdPartySdkSessionResponse, | ||||
|         api_models::payments::NoThirdPartySdkSessionResponse, | ||||
|         api_models::payments::SecretInfoToInitiateSdk, | ||||
|         api_models::payments::ApplePayPaymentRequest, | ||||
|         api_models::payments::AmountInfo, | ||||
|         api_models::payments::GooglePayWalletData, | ||||
| @ -215,6 +218,8 @@ Never share your secret api keys. Keep them guarded and secure. | ||||
|         api_models::payments::KlarnaSessionTokenResponse, | ||||
|         api_models::payments::PaypalSessionTokenResponse, | ||||
|         api_models::payments::ApplepaySessionTokenResponse, | ||||
|         api_models::payments::SdkNextAction, | ||||
|         api_models::payments::NextActionCall, | ||||
|         api_models::payments::GpayTokenizationData, | ||||
|         api_models::payments::GooglePayPaymentMethodInfo, | ||||
|         api_models::payments::ApplePayWalletData, | ||||
|  | ||||
| @ -375,6 +375,7 @@ pub enum PaymentsResponseData { | ||||
|     }, | ||||
|     SessionResponse { | ||||
|         session_token: api::SessionToken, | ||||
|         response_id: Option<String>, | ||||
|     }, | ||||
|     SessionTokenResponse { | ||||
|         session_token: String, | ||||
|  | ||||
| @ -159,6 +159,9 @@ pub enum PaymentAttemptUpdate { | ||||
|         error_code: Option<Option<String>>, | ||||
|         error_message: Option<Option<String>>, | ||||
|     }, | ||||
|     SessionUpdate { | ||||
|         connector_transaction_id: Option<String>, | ||||
|     }, | ||||
|     StatusUpdate { | ||||
|         status: storage_enums::AttemptStatus, | ||||
|     }, | ||||
| @ -390,6 +393,12 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal { | ||||
|                 preprocessing_step_id, | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|             PaymentAttemptUpdate::SessionUpdate { | ||||
|                 connector_transaction_id, | ||||
|             } => Self { | ||||
|                 connector_transaction_id, | ||||
|                 ..Default::default() | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user