diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index ff9e93c2e9..06504e4a97 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -1496,10 +1496,45 @@ impl services::ConnectorRedirectResponse for Braintree { fn get_flow_type( &self, _query_params: &str, - _json_payload: Option, - _action: services::PaymentAction, + json_payload: Option, + action: services::PaymentAction, ) -> CustomResult { - Ok(payments::CallConnectorAction::Trigger) + match action { + services::PaymentAction::PSync => match json_payload { + Some(payload) => { + let redirection_response:braintree_graphql_transformers::BraintreeRedirectionResponse = serde_json::from_value(payload) + .into_report() + .change_context( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "redirection_response", + }, + )?; + let braintree_payload = + serde_json::from_str::< + braintree_graphql_transformers::BraintreeThreeDsErrorResponse, + >(&redirection_response.authentication_response); + let (error_code, error_message) = match braintree_payload { + Ok(braintree_response_payload) => ( + braintree_response_payload.code, + braintree_response_payload.message, + ), + Err(_) => ( + consts::NO_ERROR_CODE.to_string(), + redirection_response.authentication_response, + ), + }; + Ok(payments::CallConnectorAction::StatusUpdate { + status: enums::AttemptStatus::AuthenticationFailed, + error_code: Some(error_code), + error_message: Some(error_message), + }) + } + None => Ok(payments::CallConnectorAction::Avoid), + }, + services::PaymentAction::CompleteAuthorize => { + Ok(payments::CallConnectorAction::Trigger) + } + } } } diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index 8e9b15ef46..e967caaba8 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -1,3 +1,4 @@ +use common_utils::pii; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -77,8 +78,19 @@ pub enum BraintreePaymentsRequest { #[derive(Debug, Deserialize)] pub struct BraintreeMeta { - merchant_account_id: Option>, - merchant_config_currency: Option, + merchant_account_id: Secret, + merchant_config_currency: types::storage::enums::Currency, +} + +impl TryFrom<&Option> for BraintreeMeta { + type Error = error_stack::Report; + fn try_from(meta_data: &Option) -> Result { + let metadata: Self = utils::to_connector_meta_from_secret::(meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConfig { + field_name: "merchant connector account metadata", + })?; + Ok(metadata) + } } #[derive(Debug, Serialize)] @@ -96,10 +108,13 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> item: &BraintreeRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConfig { + field_name: "merchant connector account metadata", + })?; utils::validate_currency( item.router_data.request.currency, - metadata.merchant_config_currency, + Some(metadata.merchant_config_currency), )?; match item.router_data.request.payment_method_data.clone() { @@ -140,26 +155,28 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> fn try_from( item: &BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - Some(api::PaymentMethodData::Card(_)) => { + match item.router_data.payment_method { + api_models::enums::PaymentMethod::Card => { Ok(Self::Card(CardPaymentRequest::try_from(item)?)) } - Some(api_models::payments::PaymentMethodData::CardRedirect(_)) - | Some(api_models::payments::PaymentMethodData::Wallet(_)) - | Some(api_models::payments::PaymentMethodData::PayLater(_)) - | Some(api_models::payments::PaymentMethodData::BankRedirect(_)) - | Some(api_models::payments::PaymentMethodData::BankDebit(_)) - | Some(api_models::payments::PaymentMethodData::BankTransfer(_)) - | Some(api_models::payments::PaymentMethodData::Crypto(_)) - | Some(api_models::payments::PaymentMethodData::MandatePayment) - | Some(api_models::payments::PaymentMethodData::Reward) - | Some(api_models::payments::PaymentMethodData::Upi(_)) - | Some(api_models::payments::PaymentMethodData::Voucher(_)) - | Some(api_models::payments::PaymentMethodData::GiftCard(_)) - | None => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("complete authorize flow"), - ) - .into()), + api_models::enums::PaymentMethod::CardRedirect + | api_models::enums::PaymentMethod::PayLater + | api_models::enums::PaymentMethod::Wallet + | api_models::enums::PaymentMethod::BankRedirect + | api_models::enums::PaymentMethod::BankTransfer + | api_models::enums::PaymentMethod::Crypto + | api_models::enums::PaymentMethod::BankDebit + | api_models::enums::PaymentMethod::Reward + | api_models::enums::PaymentMethod::Upi + | api_models::enums::PaymentMethod::Voucher + | api_models::enums::PaymentMethod::GiftCard => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "complete authorize flow", + ), + ) + .into()) + } } } } @@ -249,7 +266,6 @@ impl *client_token_data, item.data.get_payment_method_token()?, item.data.request.payment_method_data.clone(), - item.data.request.amount, )?), mandate_reference: None, connector_metadata: None, @@ -428,7 +444,6 @@ impl *client_token_data, item.data.get_payment_method_token()?, item.data.request.payment_method_data.clone(), - item.data.request.amount, )?), mandate_reference: None, connector_metadata: None, @@ -586,11 +601,14 @@ impl TryFrom>> for Braintree item: BraintreeRouterData<&types::RefundsRouterData>, ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConfig { + field_name: "merchant connector account metadata", + })?; utils::validate_currency( item.router_data.request.currency, - metadata.merchant_config_currency, + Some(metadata.merchant_config_currency), )?; let query = REFUND_TRANSACTION_MUTATION.to_string(); let variables = BraintreeRefundVariables { @@ -598,11 +616,7 @@ impl TryFrom>> for Braintree transaction_id: item.router_data.request.connector_transaction_id.clone(), refund: RefundInputData { amount: item.amount, - merchant_account_id: metadata.merchant_account_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "merchant_account_id", - }, - )?, + merchant_account_id: metadata.merchant_account_id, }, }, }; @@ -695,9 +709,16 @@ pub struct BraintreeRSyncRequest { impl TryFrom<&types::RefundSyncRouterData> for BraintreeRSyncRequest { type Error = error_stack::Report; fn try_from(item: &types::RefundSyncRouterData) -> Result { - let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?; - utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?; + let metadata: BraintreeMeta = utils::to_connector_meta_from_secret( + item.connector_meta_data.clone(), + ) + .change_context(errors::ConnectorError::InvalidConfig { + field_name: "merchant connector account metadata", + })?; + utils::validate_currency( + item.request.currency, + Some(metadata.merchant_config_currency), + )?; let refund_id = item.request.get_connector_refund_id()?; let query = format!("query {{ search {{ refunds(input: {{ id: {{is: \"{}\"}} }}, first: 1) {{ edges {{ node {{ id status createdAt amount {{ value currencyCode }} orderId }} }} }} }} }}",refund_id); @@ -1250,6 +1271,13 @@ pub struct BraintreeThreeDsResponse { pub liability_shift_possible: bool, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BraintreeThreeDsErrorResponse { + pub code: String, + pub message: String, +} + #[derive(Debug, Deserialize)] pub struct BraintreeRedirectionResponse { pub authentication_response: String, @@ -1263,11 +1291,7 @@ impl TryFrom for BraintreeClientTokenRequest { variables: VariableClientTokenInput { input: InputClientTokenData { client_token: ClientTokenInput { - merchant_account_id: metadata.merchant_account_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "merchant_account_id", - }, - )?, + merchant_account_id: metadata.merchant_account_id, }, }, }, @@ -1304,11 +1328,7 @@ impl }, transaction: TransactionBody { amount: item.amount.to_owned(), - merchant_account_id: metadata.merchant_account_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "merchant_account_id", - }, - )?, + merchant_account_id: metadata.merchant_account_id, }, }, }, @@ -1324,10 +1344,13 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> item: &BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>, ) -> Result { let metadata: BraintreeMeta = - utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConfig { + field_name: "merchant connector account metadata", + })?; utils::validate_currency( item.router_data.request.currency, - metadata.merchant_config_currency, + Some(metadata.merchant_config_currency), )?; let payload_data = utils::PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload( @@ -1360,11 +1383,7 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> payment_method_id: three_ds_data.nonce, transaction: TransactionBody { amount: item.amount.to_owned(), - merchant_account_id: metadata.merchant_account_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "merchant_account_id", - }, - )?, + merchant_account_id: metadata.merchant_account_id, }, }, }, @@ -1376,7 +1395,6 @@ fn get_braintree_redirect_form( client_token_data: ClientTokenResponse, payment_method_token: types::PaymentMethodToken, card_details: api_models::payments::PaymentMethodData, - amount: i64, ) -> Result> { Ok(services::RedirectForm::Braintree { client_token: client_token_data @@ -1409,7 +1427,6 @@ fn get_braintree_redirect_form( errors::ConnectorError::NotImplemented("given payment method".to_owned()), )?, }, - amount, }) } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 226d77d598..7da0b73f74 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3,6 +3,7 @@ use common_utils::{ crypto::{generate_cryptographically_secure_random_string, OptionalSecretValue}, date_time, ext_traits::{AsyncExt, ConfigExt, Encode, ValueExt}, + pii, }; use data_models::MerchantStorageScheme; use error_stack::{report, FutureExt, ResultExt}; @@ -656,15 +657,26 @@ pub async fn create_payment_connector( expected_format: "auth_type and api_key".to_string(), })?; - validate_auth_type(req.connector_name, &auth).map_err(|err| { - if err.current_context() == &errors::ConnectorError::InvalidConnectorName { - err.change_context(errors::ApiErrorResponse::InvalidRequestData { - message: "The connector name is invalid".to_string(), - }) - } else { - err.change_context(errors::ApiErrorResponse::InvalidRequestData { - message: "The auth type is invalid for the connector".to_string(), - }) + validate_auth_and_metadata_type(req.connector_name, &auth, &req.metadata).map_err(|err| { + match *err.current_context() { + errors::ConnectorError::InvalidConnectorName => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The connector name is invalid".to_string(), + }) + } + errors::ConnectorError::InvalidConfig { field_name } => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: format!("The {} is invalid", field_name), + }) + } + errors::ConnectorError::FailedToObtainAuthType => { + err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The auth type is invalid for the connector".to_string(), + }) + } + _ => err.change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "The request body is invalid".to_string(), + }), } })?; @@ -1250,9 +1262,10 @@ pub async fn update_business_profile( )) } -pub(crate) fn validate_auth_type( +pub(crate) fn validate_auth_and_metadata_type( connector_name: api_models::enums::Connector, val: &types::ConnectorAuthType, + connector_meta_data: &Option, ) -> Result<(), error_stack::Report> { use crate::connector::*; @@ -1302,6 +1315,9 @@ pub(crate) fn validate_auth_type( } api_enums::Connector::Braintree => { braintree::transformers::BraintreeAuthType::try_from(val)?; + braintree::braintree_graphql_transformers::BraintreeMeta::try_from( + connector_meta_data, + )?; Ok(()) } api_enums::Connector::Cashtocode => { diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 6b205434a8..9d75904ef6 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -178,6 +178,8 @@ pub enum ConnectorError { message: String, connector: &'static str, }, + #[error("Invalid Configuration")] + InvalidConfig { field_name: &'static str }, } #[derive(Debug, thiserror::Error)] diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index b56e55b6c7..8506e26f58 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -703,7 +703,6 @@ pub enum RedirectForm { client_token: String, card_token: String, bin: String, - amount: i64, }, } @@ -1262,7 +1261,6 @@ pub fn build_redirection_form( client_token, card_token, bin, - amount, } => { maud::html! { (maud::DOCTYPE) @@ -1270,7 +1268,7 @@ pub fn build_redirection_form( head { meta name="viewport" content="width=device-width, initial-scale=1"; (PreEscaped(r#""#)) - (PreEscaped(r#""#)) + // (PreEscaped(r#""#)) } body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { @@ -1318,15 +1316,26 @@ pub fn build_redirection_form( }} }}, onLookupComplete: function(data, next) {{ - console.log(\"onLookup Complete\", data); + // console.log(\"onLookup Complete\", data); next(); }} }}, function(err, payload) {{ if(err) {{ console.error(err); + var f = document.createElement('form'); + f.action=window.location.pathname.replace(/payments\\/redirect\\/(\\w+)\\/(\\w+)\\/\\w+/, \"payments/$1/$2/redirect/response/braintree\"); + var i = document.createElement('input'); + i.type = 'hidden'; + f.method='POST'; + i.name = 'authentication_response'; + i.value = JSON.stringify(err); + f.appendChild(i); + f.body = JSON.stringify(err); + document.body.appendChild(f); + f.submit(); }} else {{ - console.log(payload); + // console.log(payload); var f = document.createElement('form'); f.action=window.location.pathname.replace(/payments\\/redirect\\/(\\w+)\\/(\\w+)\\/\\w+/, \"payments/$1/$2/redirect/complete/braintree\"); var i = document.createElement('input');