mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(pm_auth): pm_auth service migration (#3047)
Co-authored-by: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sarthak Soni <sarthak.soni@juspay.in>
This commit is contained in:
		
							
								
								
									
										729
									
								
								crates/router/src/core/pm_auth.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										729
									
								
								crates/router/src/core/pm_auth.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,729 @@ | ||||
| use std::{collections::HashMap, str::FromStr}; | ||||
|  | ||||
| use api_models::{ | ||||
|     enums, | ||||
|     payment_methods::{self, BankAccountAccessCreds}, | ||||
|     payments::{AddressDetails, BankDebitBilling, BankDebitData, PaymentMethodData}, | ||||
| }; | ||||
| use hex; | ||||
| pub mod helpers; | ||||
| pub mod transformers; | ||||
|  | ||||
| use common_utils::{ | ||||
|     consts, | ||||
|     crypto::{HmacSha256, SignMessage}, | ||||
|     ext_traits::AsyncExt, | ||||
|     generate_id, | ||||
| }; | ||||
| use data_models::payments::PaymentIntent; | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| #[cfg(feature = "kms")] | ||||
| pub use external_services::kms; | ||||
| use helpers::PaymentAuthConnectorDataExt; | ||||
| use masking::{ExposeInterface, PeekInterface}; | ||||
| use pm_auth::{ | ||||
|     connector::plaid::transformers::PlaidAuthType, | ||||
|     types::{ | ||||
|         self as pm_auth_types, | ||||
|         api::{ | ||||
|             auth_service::{BankAccountCredentials, ExchangeToken, LinkToken}, | ||||
|             BoxedConnectorIntegration, PaymentAuthConnectorData, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     core::{ | ||||
|         errors::{self, ApiErrorResponse, RouterResponse, RouterResult, StorageErrorExt}, | ||||
|         payment_methods::cards, | ||||
|         payments::helpers as oss_helpers, | ||||
|         pm_auth::helpers::{self as pm_auth_helpers}, | ||||
|     }, | ||||
|     db::StorageInterface, | ||||
|     logger, | ||||
|     routes::AppState, | ||||
|     services::{ | ||||
|         pm_auth::{self as pm_auth_services}, | ||||
|         ApplicationResponse, | ||||
|     }, | ||||
|     types::{ | ||||
|         self, | ||||
|         domain::{self, types::decrypt}, | ||||
|         storage, | ||||
|         transformers::ForeignTryFrom, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| pub async fn create_link_token( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     payload: api_models::pm_auth::LinkTokenCreateRequest, | ||||
| ) -> RouterResponse<api_models::pm_auth::LinkTokenCreateResponse> { | ||||
|     let db = &*state.store; | ||||
|  | ||||
|     let redis_conn = db | ||||
|         .get_redis_conn() | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to get redis connection")?; | ||||
|  | ||||
|     let pm_auth_key = format!("pm_auth_{}", payload.payment_id); | ||||
|  | ||||
|     let pm_auth_configs = redis_conn | ||||
|         .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>( | ||||
|             pm_auth_key.as_str(), | ||||
|             "Vec<PaymentMethodAuthConnectorChoice>", | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to get payment method auth choices from redis")?; | ||||
|  | ||||
|     let selected_config = pm_auth_configs | ||||
|         .into_iter() | ||||
|         .find(|config| { | ||||
|             config.payment_method == payload.payment_method | ||||
|                 && config.payment_method_type == payload.payment_method_type | ||||
|         }) | ||||
|         .ok_or(ApiErrorResponse::GenericNotFoundError { | ||||
|             message: "payment method auth connector name not found".to_string(), | ||||
|         }) | ||||
|         .into_report()?; | ||||
|  | ||||
|     let connector_name = selected_config.connector_name.as_str(); | ||||
|  | ||||
|     let connector = PaymentAuthConnectorData::get_connector_by_name(connector_name)?; | ||||
|     let connector_integration: BoxedConnectorIntegration< | ||||
|         '_, | ||||
|         LinkToken, | ||||
|         pm_auth_types::LinkTokenRequest, | ||||
|         pm_auth_types::LinkTokenResponse, | ||||
|     > = connector.connector.get_connector_integration(); | ||||
|  | ||||
|     let payment_intent = oss_helpers::verify_payment_intent_time_and_client_secret( | ||||
|         &*state.store, | ||||
|         &merchant_account, | ||||
|         payload.client_secret, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     let billing_country = payment_intent | ||||
|         .as_ref() | ||||
|         .async_map(|pi| async { | ||||
|             oss_helpers::get_address_by_id( | ||||
|                 &*state.store, | ||||
|                 pi.billing_address_id.clone(), | ||||
|                 &key_store, | ||||
|                 pi.payment_id.clone(), | ||||
|                 merchant_account.merchant_id.clone(), | ||||
|                 merchant_account.storage_scheme, | ||||
|             ) | ||||
|             .await | ||||
|         }) | ||||
|         .await | ||||
|         .transpose()? | ||||
|         .flatten() | ||||
|         .and_then(|address| address.country) | ||||
|         .map(|country| country.to_string()); | ||||
|  | ||||
|     let merchant_connector_account = state | ||||
|         .store | ||||
|         .find_by_merchant_connector_account_merchant_id_merchant_connector_id( | ||||
|             merchant_account.merchant_id.as_str(), | ||||
|             &selected_config.mca_id, | ||||
|             &key_store, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||
|             id: merchant_account.merchant_id.clone(), | ||||
|         })?; | ||||
|  | ||||
|     let auth_type = helpers::get_connector_auth_type(merchant_connector_account)?; | ||||
|  | ||||
|     let router_data = pm_auth_types::LinkTokenRouterData { | ||||
|         flow: std::marker::PhantomData, | ||||
|         merchant_id: Some(merchant_account.merchant_id), | ||||
|         connector: Some(connector_name.to_string()), | ||||
|         request: pm_auth_types::LinkTokenRequest { | ||||
|             client_name: "HyperSwitch".to_string(), | ||||
|             country_codes: Some(vec![billing_country.ok_or( | ||||
|                 errors::ApiErrorResponse::MissingRequiredField { | ||||
|                     field_name: "billing_country", | ||||
|                 }, | ||||
|             )?]), | ||||
|             language: payload.language, | ||||
|             user_info: payment_intent.and_then(|pi| pi.customer_id), | ||||
|         }, | ||||
|         response: Ok(pm_auth_types::LinkTokenResponse { | ||||
|             link_token: "".to_string(), | ||||
|         }), | ||||
|         connector_http_status_code: None, | ||||
|         connector_auth_type: auth_type, | ||||
|     }; | ||||
|  | ||||
|     let connector_resp = pm_auth_services::execute_connector_processing_step( | ||||
|         state.as_ref(), | ||||
|         connector_integration, | ||||
|         &router_data, | ||||
|         &connector.connector_name, | ||||
|     ) | ||||
|     .await | ||||
|     .change_context(ApiErrorResponse::InternalServerError) | ||||
|     .attach_printable("Failed while calling link token creation connector api")?; | ||||
|  | ||||
|     let link_token_resp = | ||||
|         connector_resp | ||||
|             .response | ||||
|             .map_err(|err| ApiErrorResponse::ExternalConnectorError { | ||||
|                 code: err.code, | ||||
|                 message: err.message, | ||||
|                 connector: connector.connector_name.to_string(), | ||||
|                 status_code: err.status_code, | ||||
|                 reason: err.reason, | ||||
|             })?; | ||||
|  | ||||
|     let response = api_models::pm_auth::LinkTokenCreateResponse { | ||||
|         link_token: link_token_resp.link_token, | ||||
|         connector: connector.connector_name.to_string(), | ||||
|     }; | ||||
|  | ||||
|     Ok(ApplicationResponse::Json(response)) | ||||
| } | ||||
|  | ||||
| impl ForeignTryFrom<&types::ConnectorAuthType> for PlaidAuthType { | ||||
|     type Error = errors::ConnectorError; | ||||
|  | ||||
|     fn foreign_try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { | ||||
|         match auth_type { | ||||
|             types::ConnectorAuthType::BodyKey { api_key, key1 } => { | ||||
|                 Ok::<Self, errors::ConnectorError>(Self { | ||||
|                     client_id: api_key.to_owned(), | ||||
|                     secret: key1.to_owned(), | ||||
|                 }) | ||||
|             } | ||||
|             _ => Err(errors::ConnectorError::FailedToObtainAuthType), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn exchange_token_core( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     payload: api_models::pm_auth::ExchangeTokenCreateRequest, | ||||
| ) -> RouterResponse<()> { | ||||
|     let db = &*state.store; | ||||
|  | ||||
|     let config = get_selected_config_from_redis(db, &payload).await?; | ||||
|  | ||||
|     let connector_name = config.connector_name.as_str(); | ||||
|  | ||||
|     let connector = | ||||
|         pm_auth_types::api::PaymentAuthConnectorData::get_connector_by_name(connector_name)?; | ||||
|  | ||||
|     let merchant_connector_account = state | ||||
|         .store | ||||
|         .find_by_merchant_connector_account_merchant_id_merchant_connector_id( | ||||
|             merchant_account.merchant_id.as_str(), | ||||
|             &config.mca_id, | ||||
|             &key_store, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||
|             id: merchant_account.merchant_id.clone(), | ||||
|         })?; | ||||
|  | ||||
|     let auth_type = helpers::get_connector_auth_type(merchant_connector_account.clone())?; | ||||
|  | ||||
|     let access_token = get_access_token_from_exchange_api( | ||||
|         &connector, | ||||
|         connector_name, | ||||
|         &payload, | ||||
|         &auth_type, | ||||
|         &state, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     let bank_account_details_resp = get_bank_account_creds( | ||||
|         connector, | ||||
|         &merchant_account, | ||||
|         connector_name, | ||||
|         &access_token, | ||||
|         auth_type, | ||||
|         &state, | ||||
|         None, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     Box::pin(store_bank_details_in_payment_methods( | ||||
|         key_store, | ||||
|         payload, | ||||
|         merchant_account, | ||||
|         state, | ||||
|         bank_account_details_resp, | ||||
|         (connector_name, access_token), | ||||
|         merchant_connector_account.merchant_connector_id, | ||||
|     )) | ||||
|     .await?; | ||||
|  | ||||
|     Ok(ApplicationResponse::StatusOk) | ||||
| } | ||||
|  | ||||
| async fn store_bank_details_in_payment_methods( | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     payload: api_models::pm_auth::ExchangeTokenCreateRequest, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     state: AppState, | ||||
|     bank_account_details_resp: pm_auth_types::BankAccountCredentialsResponse, | ||||
|     connector_details: (&str, String), | ||||
|     mca_id: String, | ||||
| ) -> RouterResult<()> { | ||||
|     let key = key_store.key.get_inner().peek(); | ||||
|     let db = &*state.clone().store; | ||||
|     let (connector_name, access_token) = connector_details; | ||||
|  | ||||
|     let payment_intent = db | ||||
|         .find_payment_intent_by_payment_id_merchant_id( | ||||
|             &payload.payment_id, | ||||
|             &merchant_account.merchant_id, | ||||
|             merchant_account.storage_scheme, | ||||
|         ) | ||||
|         .await | ||||
|         .to_not_found_response(ApiErrorResponse::PaymentNotFound)?; | ||||
|  | ||||
|     let customer_id = payment_intent | ||||
|         .customer_id | ||||
|         .ok_or(ApiErrorResponse::CustomerNotFound)?; | ||||
|  | ||||
|     let payment_methods = db | ||||
|         .find_payment_method_by_customer_id_merchant_id_list( | ||||
|             &customer_id, | ||||
|             &merchant_account.merchant_id, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(ApiErrorResponse::InternalServerError)?; | ||||
|  | ||||
|     let mut hash_to_payment_method: HashMap< | ||||
|         String, | ||||
|         ( | ||||
|             storage::PaymentMethod, | ||||
|             payment_methods::PaymentMethodDataBankCreds, | ||||
|         ), | ||||
|     > = HashMap::new(); | ||||
|  | ||||
|     for pm in payment_methods { | ||||
|         if pm.payment_method == enums::PaymentMethod::BankDebit { | ||||
|             let bank_details_pm_data = decrypt::<serde_json::Value, masking::WithType>( | ||||
|                 pm.payment_method_data.clone(), | ||||
|                 key, | ||||
|             ) | ||||
|             .await | ||||
|             .change_context(ApiErrorResponse::InternalServerError) | ||||
|             .attach_printable("unable to decrypt bank account details")? | ||||
|             .map(|x| x.into_inner().expose()) | ||||
|             .map(|v| { | ||||
|                 serde_json::from_value::<payment_methods::PaymentMethodsData>(v) | ||||
|                     .into_report() | ||||
|                     .change_context(errors::StorageError::DeserializationFailed) | ||||
|                     .attach_printable("Failed to deserialize Payment Method Auth config") | ||||
|             }) | ||||
|             .transpose() | ||||
|             .unwrap_or_else(|err| { | ||||
|                 logger::error!(error=?err); | ||||
|                 None | ||||
|             }) | ||||
|             .and_then(|pmd| match pmd { | ||||
|                 payment_methods::PaymentMethodsData::BankDetails(bank_creds) => Some(bank_creds), | ||||
|                 _ => None, | ||||
|             }) | ||||
|             .ok_or(ApiErrorResponse::InternalServerError)?; | ||||
|  | ||||
|             hash_to_payment_method.insert( | ||||
|                 bank_details_pm_data.hash.clone(), | ||||
|                 (pm, bank_details_pm_data), | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "kms")] | ||||
|     let pm_auth_key = kms::get_kms_client(&state.conf.kms) | ||||
|         .await | ||||
|         .decrypt(state.conf.payment_method_auth.pm_auth_key.clone()) | ||||
|         .await | ||||
|         .change_context(ApiErrorResponse::InternalServerError)?; | ||||
|  | ||||
|     #[cfg(not(feature = "kms"))] | ||||
|     let pm_auth_key = state.conf.payment_method_auth.pm_auth_key.clone(); | ||||
|  | ||||
|     let mut update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)> = | ||||
|         Vec::new(); | ||||
|     let mut new_entries: Vec<storage::PaymentMethodNew> = Vec::new(); | ||||
|  | ||||
|     for creds in bank_account_details_resp.credentials { | ||||
|         let hash_string = format!("{}-{}", creds.account_number, creds.routing_number); | ||||
|         let generated_hash = hex::encode( | ||||
|             HmacSha256::sign_message(&HmacSha256, pm_auth_key.as_bytes(), hash_string.as_bytes()) | ||||
|                 .change_context(ApiErrorResponse::InternalServerError) | ||||
|                 .attach_printable("Failed to sign the message")?, | ||||
|         ); | ||||
|  | ||||
|         let contains_account = hash_to_payment_method.get(&generated_hash); | ||||
|         let mut pmd = payment_methods::PaymentMethodDataBankCreds { | ||||
|             mask: creds | ||||
|                 .account_number | ||||
|                 .chars() | ||||
|                 .rev() | ||||
|                 .take(4) | ||||
|                 .collect::<String>() | ||||
|                 .chars() | ||||
|                 .rev() | ||||
|                 .collect::<String>(), | ||||
|             hash: generated_hash, | ||||
|             account_type: creds.account_type, | ||||
|             account_name: creds.account_name, | ||||
|             payment_method_type: creds.payment_method_type, | ||||
|             connector_details: vec![payment_methods::BankAccountConnectorDetails { | ||||
|                 connector: connector_name.to_string(), | ||||
|                 mca_id: mca_id.clone(), | ||||
|                 access_token: payment_methods::BankAccountAccessCreds::AccessToken( | ||||
|                     access_token.clone(), | ||||
|                 ), | ||||
|                 account_id: creds.account_id, | ||||
|             }], | ||||
|         }; | ||||
|  | ||||
|         if let Some((pm, details)) = contains_account { | ||||
|             pmd.connector_details.extend( | ||||
|                 details | ||||
|                     .connector_details | ||||
|                     .clone() | ||||
|                     .into_iter() | ||||
|                     .filter(|conn| conn.mca_id != mca_id), | ||||
|             ); | ||||
|  | ||||
|             let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); | ||||
|             let encrypted_data = | ||||
|                 cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data)) | ||||
|                     .await | ||||
|                     .ok_or(ApiErrorResponse::InternalServerError)?; | ||||
|             let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { | ||||
|                 payment_method_data: Some(encrypted_data), | ||||
|             }; | ||||
|  | ||||
|             update_entries.push((pm.clone(), pm_update)); | ||||
|         } else { | ||||
|             let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); | ||||
|             let encrypted_data = | ||||
|                 cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data)) | ||||
|                     .await | ||||
|                     .ok_or(ApiErrorResponse::InternalServerError)?; | ||||
|             let pm_id = generate_id(consts::ID_LENGTH, "pm"); | ||||
|             let pm_new = storage::PaymentMethodNew { | ||||
|                 customer_id: customer_id.clone(), | ||||
|                 merchant_id: merchant_account.merchant_id.clone(), | ||||
|                 payment_method_id: pm_id, | ||||
|                 payment_method: enums::PaymentMethod::BankDebit, | ||||
|                 payment_method_type: Some(creds.payment_method_type), | ||||
|                 payment_method_issuer: None, | ||||
|                 scheme: None, | ||||
|                 metadata: None, | ||||
|                 payment_method_data: Some(encrypted_data), | ||||
|                 ..storage::PaymentMethodNew::default() | ||||
|             }; | ||||
|  | ||||
|             new_entries.push(pm_new); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     store_in_db(update_entries, new_entries, db).await?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| async fn store_in_db( | ||||
|     update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)>, | ||||
|     new_entries: Vec<storage::PaymentMethodNew>, | ||||
|     db: &dyn StorageInterface, | ||||
| ) -> RouterResult<()> { | ||||
|     let update_entries_futures = update_entries | ||||
|         .into_iter() | ||||
|         .map(|(pm, pm_update)| db.update_payment_method(pm, pm_update)) | ||||
|         .collect::<Vec<_>>(); | ||||
|  | ||||
|     let new_entries_futures = new_entries | ||||
|         .into_iter() | ||||
|         .map(|pm_new| db.insert_payment_method(pm_new)) | ||||
|         .collect::<Vec<_>>(); | ||||
|  | ||||
|     let update_futures = futures::future::join_all(update_entries_futures); | ||||
|     let new_futures = futures::future::join_all(new_entries_futures); | ||||
|  | ||||
|     let (update, new) = tokio::join!(update_futures, new_futures); | ||||
|  | ||||
|     let _ = update | ||||
|         .into_iter() | ||||
|         .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}"))); | ||||
|  | ||||
|     let _ = new | ||||
|         .into_iter() | ||||
|         .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}"))); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub async fn get_bank_account_creds( | ||||
|     connector: PaymentAuthConnectorData, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|     connector_name: &str, | ||||
|     access_token: &str, | ||||
|     auth_type: pm_auth_types::ConnectorAuthType, | ||||
|     state: &AppState, | ||||
|     bank_account_id: Option<String>, | ||||
| ) -> RouterResult<pm_auth_types::BankAccountCredentialsResponse> { | ||||
|     let connector_integration_bank_details: BoxedConnectorIntegration< | ||||
|         '_, | ||||
|         BankAccountCredentials, | ||||
|         pm_auth_types::BankAccountCredentialsRequest, | ||||
|         pm_auth_types::BankAccountCredentialsResponse, | ||||
|     > = connector.connector.get_connector_integration(); | ||||
|  | ||||
|     let router_data_bank_details = pm_auth_types::BankDetailsRouterData { | ||||
|         flow: std::marker::PhantomData, | ||||
|         merchant_id: Some(merchant_account.merchant_id.clone()), | ||||
|         connector: Some(connector_name.to_string()), | ||||
|         request: pm_auth_types::BankAccountCredentialsRequest { | ||||
|             access_token: access_token.to_string(), | ||||
|             optional_ids: bank_account_id | ||||
|                 .map(|id| pm_auth_types::BankAccountOptionalIDs { ids: vec![id] }), | ||||
|         }, | ||||
|         response: Ok(pm_auth_types::BankAccountCredentialsResponse { | ||||
|             credentials: Vec::new(), | ||||
|         }), | ||||
|         connector_http_status_code: None, | ||||
|         connector_auth_type: auth_type, | ||||
|     }; | ||||
|  | ||||
|     let bank_details_resp = pm_auth_services::execute_connector_processing_step( | ||||
|         state, | ||||
|         connector_integration_bank_details, | ||||
|         &router_data_bank_details, | ||||
|         &connector.connector_name, | ||||
|     ) | ||||
|     .await | ||||
|     .change_context(ApiErrorResponse::InternalServerError) | ||||
|     .attach_printable("Failed while calling bank account details connector api")?; | ||||
|  | ||||
|     let bank_account_details_resp = | ||||
|         bank_details_resp | ||||
|             .response | ||||
|             .map_err(|err| ApiErrorResponse::ExternalConnectorError { | ||||
|                 code: err.code, | ||||
|                 message: err.message, | ||||
|                 connector: connector.connector_name.to_string(), | ||||
|                 status_code: err.status_code, | ||||
|                 reason: err.reason, | ||||
|             })?; | ||||
|  | ||||
|     Ok(bank_account_details_resp) | ||||
| } | ||||
|  | ||||
| async fn get_access_token_from_exchange_api( | ||||
|     connector: &PaymentAuthConnectorData, | ||||
|     connector_name: &str, | ||||
|     payload: &api_models::pm_auth::ExchangeTokenCreateRequest, | ||||
|     auth_type: &pm_auth_types::ConnectorAuthType, | ||||
|     state: &AppState, | ||||
| ) -> RouterResult<String> { | ||||
|     let connector_integration: BoxedConnectorIntegration< | ||||
|         '_, | ||||
|         ExchangeToken, | ||||
|         pm_auth_types::ExchangeTokenRequest, | ||||
|         pm_auth_types::ExchangeTokenResponse, | ||||
|     > = connector.connector.get_connector_integration(); | ||||
|  | ||||
|     let router_data = pm_auth_types::ExchangeTokenRouterData { | ||||
|         flow: std::marker::PhantomData, | ||||
|         merchant_id: None, | ||||
|         connector: Some(connector_name.to_string()), | ||||
|         request: pm_auth_types::ExchangeTokenRequest { | ||||
|             public_token: payload.public_token.clone(), | ||||
|         }, | ||||
|         response: Ok(pm_auth_types::ExchangeTokenResponse { | ||||
|             access_token: "".to_string(), | ||||
|         }), | ||||
|         connector_http_status_code: None, | ||||
|         connector_auth_type: auth_type.clone(), | ||||
|     }; | ||||
|  | ||||
|     let resp = pm_auth_services::execute_connector_processing_step( | ||||
|         state, | ||||
|         connector_integration, | ||||
|         &router_data, | ||||
|         &connector.connector_name, | ||||
|     ) | ||||
|     .await | ||||
|     .change_context(ApiErrorResponse::InternalServerError) | ||||
|     .attach_printable("Failed while calling exchange token connector api")?; | ||||
|  | ||||
|     let exchange_token_resp = | ||||
|         resp.response | ||||
|             .map_err(|err| ApiErrorResponse::ExternalConnectorError { | ||||
|                 code: err.code, | ||||
|                 message: err.message, | ||||
|                 connector: connector.connector_name.to_string(), | ||||
|                 status_code: err.status_code, | ||||
|                 reason: err.reason, | ||||
|             })?; | ||||
|  | ||||
|     let access_token = exchange_token_resp.access_token; | ||||
|     Ok(access_token) | ||||
| } | ||||
|  | ||||
| async fn get_selected_config_from_redis( | ||||
|     db: &dyn StorageInterface, | ||||
|     payload: &api_models::pm_auth::ExchangeTokenCreateRequest, | ||||
| ) -> RouterResult<api_models::pm_auth::PaymentMethodAuthConnectorChoice> { | ||||
|     let redis_conn = db | ||||
|         .get_redis_conn() | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to get redis connection")?; | ||||
|  | ||||
|     let pm_auth_key = format!("pm_auth_{}", payload.payment_id); | ||||
|  | ||||
|     let pm_auth_configs = redis_conn | ||||
|         .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>( | ||||
|             pm_auth_key.as_str(), | ||||
|             "Vec<PaymentMethodAuthConnectorChoice>", | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Failed to get payment method auth choices from redis")?; | ||||
|  | ||||
|     let selected_config = pm_auth_configs | ||||
|         .iter() | ||||
|         .find(|conf| { | ||||
|             conf.payment_method == payload.payment_method | ||||
|                 && conf.payment_method_type == payload.payment_method_type | ||||
|         }) | ||||
|         .ok_or(ApiErrorResponse::GenericNotFoundError { | ||||
|             message: "connector name not found".to_string(), | ||||
|         }) | ||||
|         .into_report()? | ||||
|         .clone(); | ||||
|  | ||||
|     Ok(selected_config) | ||||
| } | ||||
|  | ||||
| pub async fn retrieve_payment_method_from_auth_service( | ||||
|     state: &AppState, | ||||
|     key_store: &domain::MerchantKeyStore, | ||||
|     auth_token: &payment_methods::BankAccountConnectorDetails, | ||||
|     payment_intent: &PaymentIntent, | ||||
| ) -> RouterResult<Option<(PaymentMethodData, enums::PaymentMethod)>> { | ||||
|     let db = state.store.as_ref(); | ||||
|  | ||||
|     let connector = pm_auth_types::api::PaymentAuthConnectorData::get_connector_by_name( | ||||
|         auth_token.connector.as_str(), | ||||
|     )?; | ||||
|  | ||||
|     let merchant_account = db | ||||
|         .find_merchant_account_by_merchant_id(&payment_intent.merchant_id, key_store) | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; | ||||
|  | ||||
|     let mca = db | ||||
|         .find_by_merchant_connector_account_merchant_id_merchant_connector_id( | ||||
|             &payment_intent.merchant_id, | ||||
|             &auth_token.mca_id, | ||||
|             key_store, | ||||
|         ) | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { | ||||
|             id: auth_token.mca_id.clone(), | ||||
|         }) | ||||
|         .attach_printable( | ||||
|             "error while fetching merchant_connector_account from merchant_id and connector name", | ||||
|         )?; | ||||
|  | ||||
|     let auth_type = pm_auth_helpers::get_connector_auth_type(mca)?; | ||||
|  | ||||
|     let BankAccountAccessCreds::AccessToken(access_token) = &auth_token.access_token; | ||||
|  | ||||
|     let bank_account_creds = get_bank_account_creds( | ||||
|         connector, | ||||
|         &merchant_account, | ||||
|         &auth_token.connector, | ||||
|         access_token, | ||||
|         auth_type, | ||||
|         state, | ||||
|         Some(auth_token.account_id.clone()), | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     logger::debug!("bank_creds: {:?}", bank_account_creds); | ||||
|  | ||||
|     let bank_account = bank_account_creds | ||||
|         .credentials | ||||
|         .first() | ||||
|         .ok_or(errors::ApiErrorResponse::InternalServerError) | ||||
|         .into_report() | ||||
|         .attach_printable("Bank account details not found")?; | ||||
|  | ||||
|     let mut bank_type = None; | ||||
|     if let Some(account_type) = bank_account.account_type.clone() { | ||||
|         bank_type = api_models::enums::BankType::from_str(account_type.as_str()) | ||||
|             .map_err(|error| logger::error!(%error,"unable to parse account_type {account_type:?}")) | ||||
|             .ok(); | ||||
|     } | ||||
|  | ||||
|     let address = oss_helpers::get_address_by_id( | ||||
|         &*state.store, | ||||
|         payment_intent.billing_address_id.clone(), | ||||
|         key_store, | ||||
|         payment_intent.payment_id.clone(), | ||||
|         merchant_account.merchant_id.clone(), | ||||
|         merchant_account.storage_scheme, | ||||
|     ) | ||||
|     .await?; | ||||
|  | ||||
|     let name = address | ||||
|         .as_ref() | ||||
|         .and_then(|addr| addr.first_name.clone().map(|name| name.into_inner())); | ||||
|  | ||||
|     let address_details = address.clone().map(|addr| { | ||||
|         let line1 = addr.line1.map(|line1| line1.into_inner()); | ||||
|         let line2 = addr.line2.map(|line2| line2.into_inner()); | ||||
|         let line3 = addr.line3.map(|line3| line3.into_inner()); | ||||
|         let zip = addr.zip.map(|zip| zip.into_inner()); | ||||
|         let state = addr.state.map(|state| state.into_inner()); | ||||
|         let first_name = addr.first_name.map(|first_name| first_name.into_inner()); | ||||
|         let last_name = addr.last_name.map(|last_name| last_name.into_inner()); | ||||
|  | ||||
|         AddressDetails { | ||||
|             city: addr.city, | ||||
|             country: addr.country, | ||||
|             line1, | ||||
|             line2, | ||||
|             line3, | ||||
|             zip, | ||||
|             state, | ||||
|             first_name, | ||||
|             last_name, | ||||
|         } | ||||
|     }); | ||||
|     let payment_method_data = PaymentMethodData::BankDebit(BankDebitData::AchBankDebit { | ||||
|         billing_details: BankDebitBilling { | ||||
|             name: name.unwrap_or_default(), | ||||
|             email: common_utils::pii::Email::from(masking::Secret::new("".to_string())), | ||||
|             address: address_details, | ||||
|         }, | ||||
|         account_number: masking::Secret::new(bank_account.account_number.clone()), | ||||
|         routing_number: masking::Secret::new(bank_account.routing_number.clone()), | ||||
|         card_holder_name: None, | ||||
|         bank_account_holder_name: None, | ||||
|         bank_name: None, | ||||
|         bank_type, | ||||
|         bank_holder_type: None, | ||||
|     }); | ||||
|  | ||||
|     Ok(Some((payment_method_data, enums::PaymentMethod::BankDebit))) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Chethan Rao
					Chethan Rao