From e58167602704b95b17edb0523d56e19ab3d2fa15 Mon Sep 17 00:00:00 2001 From: Sahebjot singh Date: Tue, 6 Dec 2022 11:55:53 +0530 Subject: [PATCH] feat: Braintree connector integration (#30) Signed-off-by: Sahebjot Singh Co-authored-by: Sahebjot Singh --- config/Development.toml | 3 + config/config.example.toml | 3 + config/docker_compose.toml | 3 + crates/router/src/configs/settings.rs | 1 + crates/router/src/connector.rs | 4 +- crates/router/src/connector/braintree.rs | 510 ++++++++++++++++++ .../src/connector/braintree/transformers.rs | 253 +++++++++ crates/router/src/lib.rs | 2 + crates/router/src/types/api.rs | 1 + crates/router/src/types/connector.rs | 1 + keys.conf | 2 + 11 files changed, 782 insertions(+), 1 deletion(-) create mode 100644 crates/router/src/connector/braintree.rs create mode 100644 crates/router/src/connector/braintree/transformers.rs diff --git a/config/Development.toml b/config/Development.toml index 35a306f83b..1f1e551307 100644 --- a/config/Development.toml +++ b/config/Development.toml @@ -49,6 +49,9 @@ base_url = "https://api.sandbox.checkout.com/" [connectors.stripe] base_url = "https://api.stripe.com/" +[connectors.braintree] +base_url = "https://api.sandbox.braintreegateway.com/" + [scheduler] stream = "SCHEDULER_STREAM" diff --git a/config/config.example.toml b/config/config.example.toml index 961fda6cb9..4583b0d881 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -105,6 +105,9 @@ base_url = "https://api.sandbox.checkout.com/" [connectors.stripe] base_url = "https://api.stripe.com/" +[connectors.braintree] +base_url = "https://api.sandbox.braintreegateway.com/" + # Scheduler settings provides a point to modify the behaviour of scheduler flow. # It defines the the streams/queues name and configuration as well as event selection variables [scheduler] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 910b97c9da..e3beee6b51 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -55,3 +55,6 @@ base_url = "https://api.sandbox.checkout.com/" [connectors.stripe] base_url = "https://api.stripe.com/" + +[connectors.braintree] +base_url = "https://api.sandbox.braintreegateway.com/" \ No newline at end of file diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index de139b1044..1a5059a324 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -100,6 +100,7 @@ pub struct Connectors { pub authorizedotnet: ConnectorParams, pub checkout: ConnectorParams, pub stripe: ConnectorParams, + pub braintree: ConnectorParams, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index e79addd546..995c14bbf9 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -1,9 +1,11 @@ pub mod aci; pub mod adyen; pub mod authorizedotnet; +pub mod braintree; pub mod checkout; pub mod stripe; pub use self::{ - aci::Aci, adyen::Adyen, authorizedotnet::Authorizedotnet, checkout::Checkout, stripe::Stripe, + aci::Aci, adyen::Adyen, authorizedotnet::Authorizedotnet, braintree::Braintree, + checkout::Checkout, stripe::Stripe, }; diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs new file mode 100644 index 0000000000..ec1e698116 --- /dev/null +++ b/crates/router/src/connector/braintree.rs @@ -0,0 +1,510 @@ +mod transformers; + +use std::fmt::Debug; + +use bytes::Bytes; +use error_stack::ResultExt; + +use self::{braintree::BraintreeAuthType, transformers as braintree}; +use crate::{ + configs::settings::Connectors, + consts, + core::{ + errors::{self, CustomResult}, + payments, + }, + headers, logger, services, + types::{ + self, + api::{self, ConnectorCommon}, + ErrorResponse, Response, + }, + utils::{self, BytesExt}, +}; + +#[derive(Debug, Clone)] +pub struct Braintree; + +impl api::ConnectorCommon for Braintree { + fn id(&self) -> &'static str { + "braintree" + } + + fn base_url(&self, connectors: Connectors) -> String { + connectors.braintree.base_url + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult, errors::ConnectorError> { + let auth: braintree::BraintreeAuthType = auth_type + .try_into() + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) + } +} + +impl api::Payment for Braintree {} + +impl api::PaymentAuthorize for Braintree {} +impl api::PaymentSync for Braintree {} +impl api::PaymentVoid for Braintree {} +impl api::PaymentCapture for Braintree {} + +#[allow(dead_code)] +impl + services::ConnectorIntegration< + api::Capture, + types::PaymentsCaptureData, + types::PaymentsResponseData, + > for Braintree +{ + // Not Implemented (R) +} + +impl + services::ConnectorIntegration + for Braintree +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + ) -> CustomResult, errors::ConnectorError> { + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsSyncType::get_content_type(self).to_string(), + ), + (headers::X_ROUTER.to_string(), "test".to_string()), + (headers::X_API_VERSION.to_string(), "6".to_string()), + (headers::ACCEPT.to_string(), "application/json".to_string()), + ]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut api_key); + Ok(headers) + } + + fn get_content_type(&self) -> &'static str { + "application/json" + } + + fn get_url( + &self, + req: &types::PaymentsSyncRouterData, + connectors: Connectors, + ) -> CustomResult { + let auth_type = braintree::BraintreeAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/merchants/{}/transactions/{}", + self.base_url(connectors), + auth_type.merchant_account, + connector_payment_id + )) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .headers(types::PaymentsSyncType::get_headers(self, req)?) + .body(types::PaymentsSyncType::get_request_body(self, req)?) + .build(), + )) + } + + fn get_error_response( + &self, + res: Bytes, + ) -> CustomResult { + let response: braintree::ErrorResponse = res + .parse_struct("Error Response") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message: response.api_error_response.message, + reason: None, + }) + } + + fn get_request_body( + &self, + _req: &types::PaymentsSyncRouterData, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + res: Response, + ) -> CustomResult { + logger::debug!(payment_sync_response=?res); + let response: braintree::BraintreePaymentsResponse = res + .response + .parse_struct("braintree PaymentsResponse") + .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) + } +} + +impl + services::ConnectorIntegration< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + > for Braintree +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + ) -> CustomResult, errors::ConnectorError> { + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self).to_string(), + ), + (headers::X_ROUTER.to_string(), "test".to_string()), + (headers::X_API_VERSION.to_string(), "6".to_string()), + (headers::ACCEPT.to_string(), "application/json".to_string()), + ]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut api_key); + Ok(headers) + } + + fn get_url( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: Connectors, + ) -> CustomResult { + let auth_type = BraintreeAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + + Ok(format!( + "{}merchants/{}/transactions", + self.base_url(connectors), + auth_type.merchant_account + )) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .headers(types::PaymentsAuthorizeType::get_headers(self, req)?) + .body(types::PaymentsAuthorizeType::get_request_body(self, req)?) + .build(), + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + ) -> CustomResult, errors::ConnectorError> { + let braintree_req = + utils::Encode::::convert_and_encode(req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(braintree_req)) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + res: Response, + ) -> CustomResult { + let response: braintree::BraintreePaymentsResponse = res + .response + .parse_struct("Braintree Payments Response") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + logger::debug!(braintreepayments_create_response=?response); + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + } + .try_into() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Bytes, + ) -> CustomResult { + logger::debug!(braintreepayments_create_response=?res); + + let response: braintree::ErrorResponse = res + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message: response.api_error_response.message, + reason: None, + }) + } +} + +#[allow(dead_code)] +impl + services::ConnectorIntegration< + api::Void, + types::PaymentsCancelData, + types::PaymentsResponseData, + > for Braintree +{ + fn get_headers( + &self, + _req: &types::PaymentsCancelRouterData, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_content_type(&self) -> &'static str { + "" + } + + fn get_url( + &self, + _req: &types::PaymentsCancelRouterData, + _connectors: Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::PaymentsCancelRouterData, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + fn build_request( + &self, + _req: &types::PaymentsCancelRouterData, + _connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn handle_response( + &self, + _data: &types::PaymentsCancelRouterData, + _res: Response, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_error_response( + &self, + _res: Bytes, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } +} + +impl api::Refund for Braintree {} +impl api::RefundExecute for Braintree {} +impl api::RefundSync for Braintree {} + +impl services::ConnectorIntegration + for Braintree +{ + fn get_headers( + &self, + req: &types::RefundsRouterData, + ) -> CustomResult, errors::ConnectorError> { + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::RefundExecuteType::get_content_type(self).to_string(), + ), + (headers::X_ROUTER.to_string(), "test".to_string()), + (headers::X_API_VERSION.to_string(), "6".to_string()), + (headers::ACCEPT.to_string(), "application/json".to_string()), + ]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut api_key); + Ok(headers) + } + + fn get_content_type(&self) -> &'static str { + "" + } + + fn get_url( + &self, + _req: &types::RefundsRouterData, + _connectors: Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + ) -> CustomResult, errors::ConnectorError> { + let braintree_req = utils::Encode::::convert_and_url_encode(req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(braintree_req)) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .headers(types::RefundExecuteType::get_headers(self, req)?) + .body(types::RefundExecuteType::get_request_body(self, req)?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + logger::debug!(target: "router::connector::braintree", response=?res); + let response: braintree::RefundResponse = res + .response + .parse_struct("braintree RefundResponse") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + } + .try_into() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + _res: Bytes, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } +} + +#[allow(dead_code)] +impl services::ConnectorIntegration + for Braintree +{ + fn get_headers( + &self, + _req: &types::RouterData, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_content_type(&self) -> &'static str { + "" + } + + fn get_url( + &self, + _req: &types::RouterData, + _connectors: Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_error_response( + &self, + _res: Bytes, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::RouterData, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } + + fn build_request( + &self, + _req: &types::RouterData, + _connectors: Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(None) + } + + fn handle_response( + &self, + data: &types::RouterData, + res: Response, + ) -> CustomResult< + types::RouterData, + errors::ConnectorError, + > { + logger::debug!(target: "router::connector::braintree", response=?res); + let response: braintree::RefundResponse = res + .response + .parse_struct("braintree RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + } + .try_into() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Braintree { + fn get_webhook_object_reference_id( + &self, + _body: &[u8], + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_webhook_event_type( + &self, + _body: &[u8], + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } + + fn get_webhook_resource_object( + &self, + _body: &[u8], + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("braintree".to_string()).into()) + } +} + +impl services::ConnectorRedirectResponse for Braintree { + fn get_flow_type( + &self, + _query_params: &str, + ) -> CustomResult { + Ok(payments::CallConnectorAction::Trigger) + } +} diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs new file mode 100644 index 0000000000..39d740cd72 --- /dev/null +++ b/crates/router/src/connector/braintree/transformers.rs @@ -0,0 +1,253 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + core::errors, + pii::PeekInterface, + types::{self, api, storage::enums}, +}; + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct DeviceData; + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct PaymentOptions { + submit_for_settlement: bool, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct BraintreePaymentsRequest { + transaction: TransactionBody, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TransactionBody { + amount: String, + device_data: DeviceData, + options: PaymentOptions, + credit_card: Card, + #[serde(rename = "type")] + kind: String, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Card { + number: String, + expiration_month: String, + expiration_year: String, + cvv: String, +} + +impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + match item.request.payment_method_data { + api::PaymentMethod::Card(ref ccard) => { + let braintree_payment_request = TransactionBody { + amount: item.request.amount.to_string(), + device_data: DeviceData {}, + options: PaymentOptions { + submit_for_settlement: true, + }, + credit_card: Card { + number: ccard.card_number.peek().clone(), + expiration_month: ccard.card_exp_month.peek().clone(), + expiration_year: ccard.card_exp_year.peek().clone(), + cvv: ccard.card_cvc.peek().clone(), + }, + kind: "sale".to_string(), + }; + Ok(BraintreePaymentsRequest { + transaction: braintree_payment_request, + }) + } + _ => Err(errors::ValidateError.into()), + } + } +} + +pub struct BraintreeAuthType { + pub(super) api_key: String, + pub(super) merchant_account: String, +} + +impl TryFrom<&types::ConnectorAuthType> for BraintreeAuthType { + type Error = error_stack::Report; + fn try_from(item: &types::ConnectorAuthType) -> Result { + if let types::ConnectorAuthType::BodyKey { api_key, key1 } = item { + Ok(Self { + api_key: api_key.to_string(), + merchant_account: key1.to_string(), + }) + } else { + Err(errors::ConnectorError::FailedToObtainAuthType)? + } + } +} + +#[derive(Debug, Clone, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BraintreePaymentStatus { + Succeeded, + Failed, + Authorized, + AuthorizedExpired, + ProcessorDeclined, + GatewayRejected, + Voided, + SubmittedForSettlement, + Settling, + Settled, + SettlementPending, + SettlementDeclined, + SettlementConfirmed, +} + +impl Default for BraintreePaymentStatus { + fn default() -> Self { + BraintreePaymentStatus::Settling + } +} + +impl From for enums::AttemptStatus { + fn from(item: BraintreePaymentStatus) -> Self { + match item { + BraintreePaymentStatus::Succeeded => enums::AttemptStatus::Charged, + BraintreePaymentStatus::AuthorizedExpired => enums::AttemptStatus::AuthorizationFailed, + BraintreePaymentStatus::Failed + | BraintreePaymentStatus::GatewayRejected + | BraintreePaymentStatus::ProcessorDeclined + | BraintreePaymentStatus::SettlementDeclined => enums::AttemptStatus::Failure, + BraintreePaymentStatus::Authorized => enums::AttemptStatus::Authorized, + BraintreePaymentStatus::Voided => enums::AttemptStatus::Voided, + _ => enums::AttemptStatus::Pending, + } + } +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BraintreePaymentsResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(types::RouterData { + response: Ok(types::PaymentsResponseData { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.transaction.id, + ), + redirection_data: None, + redirect: false, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct BraintreePaymentsResponse { + transaction: TransactionResponse, +} + +#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TransactionResponse { + id: String, + currency_iso_code: String, + amount: String, + status: BraintreePaymentStatus, +} + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorResponse { + pub api_error_response: ApiErrorResponse, +} + +#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)] +pub struct ApiErrorResponse { + pub message: String, +} + +#[derive(Default, Debug, Clone, Serialize)] +pub struct RefundRequest {} + +impl TryFrom<&types::RefundsRouterData> for RefundRequest { + type Error = error_stack::Report; + fn try_from(_item: &types::RefundsRouterData) -> Result { + Ok(RefundRequest {}) + } +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + Processing, +} + +impl Default for RefundStatus { + fn default() -> Self { + RefundStatus::Processing + } +} + +impl From for enums::RefundStatus { + fn from(item: self::RefundStatus) -> Self { + match item { + self::RefundStatus::Succeeded => enums::RefundStatus::Success, + self::RefundStatus::Failed => enums::RefundStatus::Failure, + self::RefundStatus::Processing => enums::RefundStatus::Pending, + } + } +} + +#[derive(Default, Debug, Clone, Deserialize)] +pub struct RefundResponse { + pub id: String, + pub status: RefundStatus, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(types::RouterData { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id, + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(types::RouterData { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id, + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 928e36878b..75d8fb3954 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -57,6 +57,8 @@ pub mod headers { pub const CONTENT_TYPE: &str = "Content-Type"; pub const X_ROUTER: &str = "X-router"; pub const AUTHORIZATION: &str = "Authorization"; + pub const ACCEPT: &str = "Accept"; + pub const X_API_VERSION: &str = "X-ApiVersion"; } pub fn mk_app( diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index a17f51ca51..08f413660e 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -143,6 +143,7 @@ impl ConnectorData { "aci" => Ok(Box::new(&connector::Aci)), "checkout" => Ok(Box::new(&connector::Checkout)), "authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)), + "braintree" => Ok(Box::new(&connector::Braintree)), _ => Err(report!(errors::UnexpectedError) .attach_printable(format!("invalid connector name: {connector_name}"))) .change_context(errors::ConnectorError::InvalidConnectorName) diff --git a/crates/router/src/types/connector.rs b/crates/router/src/types/connector.rs index c211eb8e22..b2c95dcac3 100644 --- a/crates/router/src/types/connector.rs +++ b/crates/router/src/types/connector.rs @@ -6,6 +6,7 @@ pub enum Connector { Checkout, Aci, Authorizedotnet, + Braintree, #[default] Dummy, } diff --git a/keys.conf b/keys.conf index 7324dee535..9471e84cf9 100644 --- a/keys.conf +++ b/keys.conf @@ -21,3 +21,5 @@ adyen_key1: aci_api_key: aci_key1: +braintree_api_key: +braintree_key1: