diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 1c7292f8eeb..1f8c3f3b5e6 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -13,7 +13,10 @@ use crate::{ configs::settings, connector::utils as connector_utils, consts, - core::errors::{self, CustomResult}, + core::{ + errors::{self, CustomResult}, + payments, + }, headers, logger, services::{ self, @@ -156,7 +159,7 @@ impl api::PaymentAuthorize for Braintree {} impl api::PaymentSync for Braintree {} impl api::PaymentVoid for Braintree {} impl api::PaymentCapture for Braintree {} - +impl api::PaymentsCompleteAuthorize for Braintree {} impl api::PaymentSession for Braintree {} impl api::ConnectorAccessToken for Braintree {} @@ -1248,3 +1251,146 @@ impl api::IncomingWebhook for Braintree { Err(errors::ConnectorError::WebhooksNotImplemented).into_report() } } + +impl services::ConnectorRedirectResponse for Braintree { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + _action: services::PaymentAction, + ) -> CustomResult { + Ok(payments::CallConnectorAction::Trigger) + } +} + +impl + ConnectorIntegration< + api::CompleteAuthorize, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + > for Braintree +{ + fn get_headers( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let connector_api_version = &req.connector_api_version; + match self.is_braintree_graphql_version(connector_api_version) { + true => self.build_headers(req, connectors), + false => Err(errors::ConnectorError::NotImplemented( + "get_headers method".to_string(), + ))?, + } + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_api_version = &req.connector_api_version; + match self.is_braintree_graphql_version(connector_api_version) { + true => { + let base_url = connectors + .braintree + .secondary_base_url + .as_ref() + .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + Ok(base_url.to_string()) + } + false => Err(errors::ConnectorError::NotImplemented( + "get_url method".to_string(), + ))?, + } + } + fn get_request_body( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + ) -> CustomResult, errors::ConnectorError> { + let connector_api_version = &req.connector_api_version; + match self.is_braintree_graphql_version(connector_api_version) { + true => { + let connector_request = + braintree_graphql_transformers::BraintreePaymentsRequest::try_from(req)?; + let braintree_payment_request = types::RequestBody::log_and_get_request_body( + &connector_request, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(braintree_payment_request)) + } + false => Err(errors::ConnectorError::NotImplemented( + "get_request_body method".to_string(), + ))?, + } + } + fn build_request( + &self, + req: &types::PaymentsCompleteAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let connector_api_version = &req.connector_api_version; + match self.is_braintree_graphql_version(connector_api_version) { + true => Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, + )?) + .build(), + )), + false => Err(errors::ConnectorError::NotImplemented( + "payment method".to_string(), + ))?, + } + } + fn handle_response( + &self, + data: &types::PaymentsCompleteAuthorizeRouterData, + res: types::Response, + ) -> CustomResult { + match connector_utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&data.request)? + { + true => { + let response: braintree_graphql_transformers::BraintreeCompleteChargeResponse = res + .response + .parse_struct("Braintree PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + false => { + let response: braintree_graphql_transformers::BraintreeCompleteAuthResponse = res + .response + .parse_struct("Braintree AuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + } + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} diff --git a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs index 28c55f2141d..913c32014aa 100644 --- a/crates/router/src/connector/braintree/braintree_graphql_transformers.rs +++ b/crates/router/src/connector/braintree/braintree_graphql_transformers.rs @@ -1,14 +1,16 @@ -use error_stack::ResultExt; -use masking::Secret; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData}, consts, core::errors, + services, types::{self, api, storage::enums}, }; +pub const CLIENT_TOKEN_MUTATION: &str = "mutation createClientToken($input: CreateClientTokenInput!) { createClientToken(input: $input) { clientToken}}"; pub const TOKENIZE_CREDIT_CARD: &str = "mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { clientMutationId paymentMethod { id } } }"; pub const CHARGE_CREDIT_CARD_MUTATION: &str = "mutation ChargeCreditCard($input: ChargeCreditCardInput!) { chargeCreditCard(input: $input) { transaction { id legacyId createdAt amount { value currencyCode } status } } }"; pub const AUTHORIZE_CREDIT_CARD_MUTATION: &str = "mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { transaction { id legacyId amount { value currencyCode } status } } }"; @@ -29,11 +31,18 @@ pub struct VariablePaymentInput { } #[derive(Debug, Serialize)] -pub struct BraintreePaymentsRequest { +pub struct CardPaymentRequest { query: String, variables: VariablePaymentInput, } +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum BraintreePaymentsRequest { + Card(CardPaymentRequest), + CardThreeDs(BraintreeClientTokenRequest), +} + #[derive(Debug, Deserialize)] pub struct BraintreeMeta { merchant_account_id: Option>, @@ -56,34 +65,13 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest { match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(_) => { - let query = match item.request.is_auto_capture()? { - true => CHARGE_CREDIT_CARD_MUTATION.to_string(), - false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), - }; - Ok(Self { - query, - variables: VariablePaymentInput { - input: PaymentInput { - payment_method_id: match item.get_payment_method_token()? { - types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? - } - }, - transaction: TransactionBody { - amount: utils::to_currency_base_unit( - item.request.amount, - item.request.currency, - )?, - merchant_account_id: metadata.merchant_account_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "merchant_account_id", - }, - )?, - }, - }, - }, - }) + if item.is_three_ds() { + Ok(Self::CardThreeDs(BraintreeClientTokenRequest::try_from( + metadata, + )?)) + } else { + Ok(Self::Card(CardPaymentRequest::try_from((item, metadata))?)) + } } api_models::payments::PaymentMethodData::CardRedirect(_) | api_models::payments::PaymentMethodData::Wallet(_) @@ -106,6 +94,33 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest { } } +impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for BraintreePaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { + match item.request.payment_method_data.clone() { + Some(api::PaymentMethodData::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()), + } + } +} + #[derive(Debug, Clone, Deserialize)] pub struct AuthResponse { data: DataAuthResponse, @@ -114,6 +129,14 @@ pub struct AuthResponse { #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum BraintreeAuthResponse { + AuthResponse(Box), + ClientTokenResponse(Box), + ErrorResponse(Box), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum BraintreeCompleteAuthResponse { AuthResponse(Box), ErrorResponse(Box), } @@ -135,13 +158,24 @@ pub struct AuthChargeCreditCard { transaction: TransactionAuthChargeResponseBody, } -impl - TryFrom> - for types::RouterData +impl + TryFrom< + types::ResponseRouterData< + F, + BraintreeAuthResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: types::ResponseRouterData< + F, + BraintreeAuthResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, ) -> Result { match item.response { BraintreeAuthResponse::ErrorResponse(error_response) => Ok(Self { @@ -164,6 +198,23 @@ impl ..item.data }) } + BraintreeAuthResponse::ClientTokenResponse(client_token_data) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(get_braintree_redirect_form( + *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, + network_txn_id: None, + connector_response_reference_id: None, + }), + ..item.data + }), } } } @@ -286,16 +337,22 @@ impl From for enums::AttemptStatus { } } -impl - TryFrom> - for types::RouterData +impl + TryFrom< + types::ResponseRouterData< + F, + BraintreePaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( item: types::ResponseRouterData< F, BraintreePaymentsResponse, - T, + types::PaymentsAuthorizeData, types::PaymentsResponseData, >, ) -> Result { @@ -307,6 +364,111 @@ impl BraintreePaymentsResponse::PaymentsResponse(payment_response) => { let transaction_data = payment_response.data.charge_credit_card.transaction; + Ok(Self { + status: enums::AttemptStatus::from(transaction_data.status.clone()), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + ..item.data + }) + } + BraintreePaymentsResponse::ClientTokenResponse(client_token_data) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Some(get_braintree_redirect_form( + *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, + network_txn_id: None, + connector_response_reference_id: None, + }), + ..item.data + }), + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BraintreeCompleteChargeResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BraintreeCompleteChargeResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BraintreeCompleteChargeResponse::ErrorResponse(error_response) => Ok(Self { + response: build_error_response(&error_response.errors.clone(), item.http_code), + ..item.data + }), + BraintreeCompleteChargeResponse::PaymentsResponse(payment_response) => { + let transaction_data = payment_response.data.charge_credit_card.transaction; + + Ok(Self { + status: enums::AttemptStatus::from(transaction_data.status.clone()), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + ..item.data + }) + } + } + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BraintreeCompleteAuthResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BraintreeCompleteAuthResponse, + types::CompleteAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response { + BraintreeCompleteAuthResponse::ErrorResponse(error_response) => Ok(Self { + response: build_error_response(&error_response.errors, item.http_code), + ..item.data + }), + BraintreeCompleteAuthResponse::AuthResponse(auth_response) => { + let transaction_data = auth_response.data.authorize_credit_card.transaction; + Ok(Self { status: enums::AttemptStatus::from(transaction_data.status.clone()), response: Ok(types::PaymentsResponseData::TransactionResponse { @@ -332,6 +494,14 @@ pub struct PaymentsResponse { #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum BraintreePaymentsResponse { + PaymentsResponse(Box), + ClientTokenResponse(Box), + ErrorResponse(Box), +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum BraintreeCompleteChargeResponse { PaymentsResponse(Box), ErrorResponse(Box), } @@ -572,23 +742,46 @@ pub struct CreditCardData { cardholder_name: Secret, } +#[derive(Default, Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientTokenInput { + merchant_account_id: Secret, +} + #[derive(Default, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct InputData { credit_card: CreditCardData, } +#[derive(Default, Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InputClientTokenData { + client_token: ClientTokenInput, +} + #[derive(Default, Debug, Clone, Serialize)] pub struct VariableInput { input: InputData, } +#[derive(Default, Debug, Clone, Serialize)] +pub struct VariableClientTokenInput { + input: InputClientTokenData, +} + #[derive(Default, Debug, Clone, Serialize)] pub struct BraintreeTokenRequest { query: String, variables: VariableInput, } +#[derive(Default, Debug, Clone, Serialize)] +pub struct BraintreeClientTokenRequest { + query: String, + variables: VariableClientTokenInput, +} + impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest { type Error = error_stack::Report; fn try_from(item: &types::TokenizationRouterData) -> Result { @@ -641,12 +834,29 @@ pub struct TokenizeCreditCardData { payment_method: TokenizePaymentMethodData, } +#[derive(Default, Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientToken { + client_token: Secret, +} + #[derive(Default, Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TokenizeCreditCard { tokenize_credit_card: TokenizeCreditCardData, } +#[derive(Default, Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientTokenData { + create_client_token: ClientToken, +} + +#[derive(Default, Debug, Clone, Deserialize)] +pub struct ClientTokenResponse { + data: ClientTokenData, +} + #[derive(Default, Debug, Clone, Deserialize)] pub struct TokenResponse { data: TokenizeCreditCard, @@ -987,3 +1197,166 @@ impl } } } + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BraintreeThreeDsResponse { + pub nonce: String, + pub liability_shifted: bool, + pub liability_shift_possible: bool, +} + +#[derive(Debug, Deserialize)] +pub struct BraintreeRedirectionResponse { + pub authentication_response: String, +} + +impl TryFrom for BraintreeClientTokenRequest { + type Error = error_stack::Report; + fn try_from(metadata: BraintreeMeta) -> Result { + Ok(Self { + query: CLIENT_TOKEN_MUTATION.to_owned(), + variables: VariableClientTokenInput { + input: InputClientTokenData { + client_token: ClientTokenInput { + merchant_account_id: metadata.merchant_account_id.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "merchant_account_id", + }, + )?, + }, + }, + }, + }) + } +} + +impl TryFrom<(&types::PaymentsAuthorizeRouterData, BraintreeMeta)> for CardPaymentRequest { + type Error = error_stack::Report; + fn try_from( + payment_info: (&types::PaymentsAuthorizeRouterData, BraintreeMeta), + ) -> Result { + let item = payment_info.0; + let metadata = payment_info.1; + let query = match item.request.is_auto_capture()? { + true => CHARGE_CREDIT_CARD_MUTATION.to_string(), + false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), + }; + Ok(Self { + query, + variables: VariablePaymentInput { + input: PaymentInput { + payment_method_id: match item.get_payment_method_token()? { + types::PaymentMethodToken::Token(token) => token, + types::PaymentMethodToken::ApplePayDecrypt(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + transaction: TransactionBody { + amount: utils::to_currency_base_unit( + item.request.amount, + item.request.currency, + )?, + merchant_account_id: metadata.merchant_account_id.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "merchant_account_id", + }, + )?, + }, + }, + }, + }) + } +} + +impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for CardPaymentRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> 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 payload_data = + utils::PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload( + &item.request, + )? + .expose(); + let redirection_response: BraintreeRedirectionResponse = + serde_json::from_value(payload_data) + .into_report() + .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "redirection_response", + })?; + let three_ds_data = serde_json::from_str::( + &redirection_response.authentication_response, + ) + .into_report() + .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "three_ds_data", + })?; + let query = + match utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&item.request)? { + true => CHARGE_CREDIT_CARD_MUTATION.to_string(), + false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(), + }; + Ok(Self { + query, + variables: VariablePaymentInput { + input: PaymentInput { + payment_method_id: three_ds_data.nonce, + transaction: TransactionBody { + amount: utils::to_currency_base_unit( + item.request.amount, + item.request.currency, + )?, + merchant_account_id: metadata.merchant_account_id.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "merchant_account_id", + }, + )?, + }, + }, + }, + }) + } +} + +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 + .data + .create_client_token + .client_token + .expose(), + card_token: match payment_method_token { + types::PaymentMethodToken::Token(token) => token, + types::PaymentMethodToken::ApplePayDecrypt(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + bin: match card_details { + api_models::payments::PaymentMethodData::Card(card_details) => { + card_details.card_number.get_card_isin() + } + api_models::payments::PaymentMethodData::CardRedirect(_) + | api_models::payments::PaymentMethodData::Wallet(_) + | api_models::payments::PaymentMethodData::PayLater(_) + | api_models::payments::PaymentMethodData::BankRedirect(_) + | api_models::payments::PaymentMethodData::BankDebit(_) + | api_models::payments::PaymentMethodData::BankTransfer(_) + | api_models::payments::PaymentMethodData::Crypto(_) + | api_models::payments::PaymentMethodData::MandatePayment + | api_models::payments::PaymentMethodData::Reward + | api_models::payments::PaymentMethodData::Upi(_) + | api_models::payments::PaymentMethodData::Voucher(_) + | api_models::payments::PaymentMethodData::GiftCard(_) => Err( + errors::ConnectorError::NotImplemented("given payment method".to_owned()), + )?, + }, + amount, + }) +} diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index a9731d04839..eda3739edfc 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -145,7 +145,6 @@ default_imp_for_complete_authorize!( connector::Aci, connector::Adyen, connector::Bitpay, - connector::Braintree, connector::Boku, connector::Cashtocode, connector::Checkout, @@ -286,7 +285,6 @@ default_imp_for_connector_redirect_response!( connector::Adyen, connector::Bitpay, connector::Boku, - connector::Braintree, connector::Cashtocode, connector::Coinbase, connector::Cryptopay, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 2f1f4323d81..8b9b0b6afeb 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -668,6 +668,12 @@ pub enum RedirectForm { payment_fields_token: String, // payment-field-token }, Payme, + Braintree { + client_token: String, + card_token: String, + bin: String, + amount: i64, + }, } impl From<(url::Url, Method)> for RedirectForm { @@ -1147,6 +1153,92 @@ pub fn build_redirection_form( ".to_string())) } } + RedirectForm::Braintree { + client_token, + card_token, + bin, + amount, + } => { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta name="viewport" content="width=device-width, initial-scale=1"; + (PreEscaped(r#""#)) + (PreEscaped(r#""#)) + + } + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" } + + (PreEscaped(r#""#)) + + (PreEscaped(r#" + + "#)) + + + h3 style="text-align: center;" { "Please wait while we process your payment..." } + } + + (PreEscaped(format!("" + ))) + }} + } } }