diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 23219414f6..90cc3d4b6a 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -7474,6 +7474,7 @@ "boku", "braintree", "cashtocode", + "chargebee", "checkout", "coinbase", "coingate", @@ -7743,7 +7744,8 @@ "payout_processor", "payment_method_auth", "authentication_processor", - "tax_processor" + "tax_processor", + "billing_processor" ] }, "ConnectorVolumeSplit": { @@ -20054,6 +20056,7 @@ "boku", "braintree", "cashtocode", + "chargebee", "checkout", "coinbase", "coingate", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 9aea807b39..a16adf76c5 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9581,6 +9581,7 @@ "boku", "braintree", "cashtocode", + "chargebee", "checkout", "coinbase", "coingate", @@ -9831,7 +9832,8 @@ "payout_processor", "payment_method_auth", "authentication_processor", - "tax_processor" + "tax_processor", + "billing_processor" ] }, "ConnectorVolumeSplit": { @@ -24508,6 +24510,7 @@ "boku", "braintree", "cashtocode", + "chargebee", "checkout", "coinbase", "coingate", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index d5606004d9..639d25ea76 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -149,6 +149,12 @@ pub enum TaxConnectors { Taxjar, } +#[derive(Clone, Debug, serde::Serialize, strum::EnumString)] +#[serde(rename_all = "snake_case")] +pub enum BillingConnectors { + Chargebee, +} + #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema, )] @@ -405,6 +411,9 @@ pub fn convert_tax_connector(connector_name: &str) -> Option { TaxConnectors::from_str(connector_name).ok() } +pub fn convert_billing_connector(connector_name: &str) -> Option { + BillingConnectors::from_str(connector_name).ok() +} #[cfg(feature = "frm")] pub fn convert_frm_connector(connector_name: &str) -> Option { FrmConnectors::from_str(connector_name).ok() diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 9cd896d5c3..c65cd99d93 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -65,7 +65,7 @@ pub enum RoutableConnectors { Boku, Braintree, Cashtocode, - // Chargebee, + Chargebee, Checkout, Coinbase, Coingate, @@ -204,7 +204,7 @@ pub enum Connector { Boku, Braintree, Cashtocode, - // Chargebee, + Chargebee, Checkout, Coinbase, Coingate, @@ -362,7 +362,7 @@ impl Connector { | Self::Boku | Self::Braintree | Self::Cashtocode - // | Self::Chargebee + | Self::Chargebee | Self::Coinbase |Self::Coingate | Self::Cryptopay @@ -494,6 +494,7 @@ impl From for Connector { RoutableConnectors::Boku => Self::Boku, RoutableConnectors::Braintree => Self::Braintree, RoutableConnectors::Cashtocode => Self::Cashtocode, + RoutableConnectors::Chargebee => Self::Chargebee, RoutableConnectors::Checkout => Self::Checkout, RoutableConnectors::Coinbase => Self::Coinbase, RoutableConnectors::Cryptopay => Self::Cryptopay, diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 841b8fbc4d..7b6e4651fb 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -429,6 +429,9 @@ pub enum ConnectorType { AuthenticationProcessor, /// Tax Calculation Processor TaxProcessor, + /// Represents billing processors that handle subscription management, invoicing, + /// and recurring payments. Examples include Chargebee, Recurly, and Stripe Billing. + BillingProcessor, } #[derive(Debug, Eq, PartialEq)] diff --git a/crates/common_utils/src/id_type/payment.rs b/crates/common_utils/src/id_type/payment.rs index 33bf9d2417..611f59bb04 100644 --- a/crates/common_utils/src/id_type/payment.rs +++ b/crates/common_utils/src/id_type/payment.rs @@ -86,3 +86,12 @@ impl From for router_env::opentelemetry::Value { Self::from(val.0 .0 .0) } } + +impl std::str::FromStr for PaymentReferenceId { + type Err = error_stack::Report; + + fn from_str(s: &str) -> Result { + let cow_string = std::borrow::Cow::Owned(s.to_string()); + Self::try_from(cow_string) + } +} diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 34ce0a7f00..8920e39b5b 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -172,6 +172,7 @@ pub struct ConnectorConfig { pub boku: Option, pub braintree: Option, pub cashtocode: Option, + pub chargebee: Option, pub checkout: Option, pub coinbase: Option, pub coingate: Option, @@ -342,6 +343,7 @@ impl ConnectorConfig { Connector::Boku => Ok(connector_data.boku), Connector::Braintree => Ok(connector_data.braintree), Connector::Cashtocode => Ok(connector_data.cashtocode), + Connector::Chargebee => Ok(connector_data.chargebee), Connector::Checkout => Ok(connector_data.checkout), Connector::Coinbase => Ok(connector_data.coinbase), Connector::Coingate => Ok(connector_data.coingate), diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 7785bf5721..353e99a163 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -9,7 +9,8 @@ license.workspace = true frm = ["hyperswitch_domain_models/frm", "hyperswitch_interfaces/frm"] payouts = ["hyperswitch_domain_models/payouts", "api_models/payouts", "hyperswitch_interfaces/payouts"] v1 = ["api_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"] -v2 = ["api_models/v2", "hyperswitch_domain_models/v2", "common_utils/v2"] +v2 = ["api_models/v2", "hyperswitch_domain_models/v2", "common_utils/v2","hyperswitch_interfaces/v2"] +revenue_recovery = ["hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery"] [dependencies] actix-http = "3.6.0" diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee.rs b/crates/hyperswitch_connectors/src/connectors/chargebee.rs index 40dd137c28..73eeabe435 100644 --- a/crates/hyperswitch_connectors/src/connectors/chargebee.rs +++ b/crates/hyperswitch_connectors/src/connectors/chargebee.rs @@ -1,5 +1,6 @@ pub mod transformers; +use base64::Engine; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -7,6 +8,8 @@ use common_utils::{ types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, }; use error_stack::{report, ResultExt}; +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +use hyperswitch_domain_models::revenue_recovery; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ @@ -36,7 +39,7 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, PeekInterface, Secret}; use transformers as chargebee; use crate::{constants::headers, types::ResponseRouterData, utils}; @@ -543,13 +546,88 @@ impl ConnectorIntegration for Chargebee #[async_trait::async_trait] impl webhooks::IncomingWebhook for Chargebee { + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let base64_signature = utils::get_header_key_value("authorization", request.headers)?; + let signature = base64_signature.as_bytes().to_owned(); + Ok(signature) + } + async fn verify_webhook_source( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + merchant_id: &common_utils::id_type::MerchantId, + connector_webhook_details: Option, + _connector_account_details: common_utils::crypto::Encryptable>, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_id, + connector_label, + connector_webhook_details, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signature = self + .get_webhook_source_verification_signature(request, &connector_webhook_secrets) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let password = connector_webhook_secrets + .additional_secret + .ok_or(errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable("Failed to get additional secrets")?; + let username = String::from_utf8(connector_webhook_secrets.secret.to_vec()) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable("Could not convert secret to UTF-8")?; + let secret_auth = format!( + "Basic {}", + base64::engine::general_purpose::STANDARD.encode(format!( + "{}:{}", + username, + password.peek() + )) + ); + let signature_auth = String::from_utf8(signature.to_vec()) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable("Could not convert secret to UTF-8")?; + Ok(signature_auth == secret_auth) + } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_webhook_object_reference_id( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let webhook = + chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body) + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api_models::webhooks::ObjectReferenceId::InvoiceId( + api_models::webhooks::InvoiceIdType::ConnectorInvoiceId(webhook.content.invoice.id), + )) + } + #[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))] fn get_webhook_object_reference_id( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } - + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_webhook_event_type( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let webhook = + chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body) + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + let event = api_models::webhooks::IncomingWebhookEvent::from(webhook.event_type); + Ok(event) + } + #[cfg(any(feature = "v1", not(all(feature = "revenue_recovery", feature = "v2"))))] fn get_webhook_event_type( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, @@ -559,9 +637,30 @@ impl webhooks::IncomingWebhook for Chargebee { fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook = + chargebee::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body) + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(webhook)) + } + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_attempt_details( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let webhook = + transformers::ChargebeeWebhookBody::get_webhook_object_from_body(request.body)?; + revenue_recovery::RevenueRecoveryAttemptData::try_from(webhook) + } + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_invoice_details( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let webhook = + transformers::ChargebeeInvoiceBody::get_invoice_webhook_data_from_body(request.body)?; + revenue_recovery::RevenueRecoveryInvoiceData::try_from(webhook) } } diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs index 3d194caeaa..80b9b8cc20 100644 --- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs @@ -1,5 +1,15 @@ +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +use std::str::FromStr; + use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::{ + errors::CustomResult, + ext_traits::ByteSliceExt, + types::{MinorUnit, StringMinorUnit}, +}; +use error_stack::ResultExt; +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +use hyperswitch_domain_models::revenue_recovery; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, @@ -11,6 +21,7 @@ use hyperswitch_domain_models::{ use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, @@ -226,3 +237,264 @@ pub struct ChargebeeErrorResponse { pub message: String, pub reason: Option, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeWebhookBody { + pub content: ChargebeeWebhookContent, + pub event_type: ChargebeeEventType, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeInvoiceBody { + pub content: ChargebeeInvoiceContent, + pub event_type: ChargebeeEventType, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeInvoiceContent { + pub invoice: ChargebeeInvoiceData, +} + +#[derive(Serialize, Deserialize, Debug)] + +pub struct ChargebeeWebhookContent { + pub transaction: ChargebeeTransactionData, + pub invoice: ChargebeeInvoiceData, + pub customer: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChargebeeEventType { + PaymentSucceeded, + PaymentFailed, + InvoiceDeleted, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeInvoiceData { + // invoice id + pub id: String, + pub total: MinorUnit, + pub currency_code: enums::Currency, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeTransactionData { + id_at_gateway: Option, + status: ChargebeeTranasactionStatus, + error_code: Option, + error_text: Option, + gateway_account_id: Option, + currency_code: enums::Currency, + amount: MinorUnit, + #[serde(default, with = "common_utils::custom_serde::timestamp::option")] + date: Option, + payment_method: ChargebeeTransactionPaymentMethod, + payment_method_details: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChargebeeTransactionPaymentMethod { + Card, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeePaymentMethodDetails { + card: ChargebeeCardDetails, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeCardDetails { + funding_type: ChargebeeFundingType, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChargebeeFundingType { + Credit, + Debit, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChargebeeTranasactionStatus { + // Waiting for response from the payment gateway. + InProgress, + // The transaction is successful. + Success, + // Transaction failed. + Failure, + // No response received while trying to charge the card. + Timeout, + // Indicates that a successful payment transaction has failed now due to a late failure notification from the payment gateway, + // typically caused by issues like insufficient funds or a closed bank account. + LateFailure, + // Connection with Gateway got terminated abruptly. So, status of this transaction needs to be resolved manually + NeedsAttention, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeeCustomer { + pub payment_method: ChargebeePaymentMethod, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ChargebeePaymentMethod { + pub reference_id: String, + pub gateway: ChargebeeGateway, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChargebeeGateway { + Stripe, + Braintree, +} + +impl ChargebeeWebhookBody { + pub fn get_webhook_object_from_body(body: &[u8]) -> CustomResult { + let webhook_body = body + .parse_struct::("ChargebeeWebhookBody") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(webhook_body) + } +} + +impl ChargebeeInvoiceBody { + pub fn get_invoice_webhook_data_from_body( + body: &[u8], + ) -> CustomResult { + let webhook_body = body + .parse_struct::("ChargebeeInvoiceBody") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(webhook_body) + } +} + +impl ChargebeeCustomer { + // the logic to find connector customer id & mandate id is different for different gateways, reference : https://apidocs.chargebee.com/docs/api/customers?prod_cat_ver=2#customer_payment_method_reference_id . + pub fn find_connector_ids(&self) -> Result<(String, String), errors::ConnectorError> { + match self.payment_method.gateway { + ChargebeeGateway::Stripe | ChargebeeGateway::Braintree => { + let mut parts = self.payment_method.reference_id.split('/'); + let customer_id = parts + .next() + .ok_or(errors::ConnectorError::WebhookBodyDecodingFailed)? + .to_string(); + let mandate_id = parts + .last() + .ok_or(errors::ConnectorError::WebhookBodyDecodingFailed)? + .to_string(); + Ok((customer_id, mandate_id)) + } + } + } +} + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +impl TryFrom for revenue_recovery::RevenueRecoveryAttemptData { + type Error = error_stack::Report; + fn try_from(item: ChargebeeWebhookBody) -> Result { + let amount = item.content.transaction.amount; + let currency = item.content.transaction.currency_code.to_owned(); + let merchant_reference_id = + common_utils::id_type::PaymentReferenceId::from_str(&item.content.invoice.id) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let connector_transaction_id = item + .content + .transaction + .id_at_gateway + .map(common_utils::types::ConnectorTransactionId::TxnId); + let error_code = item.content.transaction.error_code.clone(); + let error_message = item.content.transaction.error_text.clone(); + let (connector_customer_id, processor_payment_method_token) = match &item.content.customer { + Some(customer) => { + let (customer_id, mandate_id) = customer.find_connector_ids()?; + (Some(customer_id), Some(mandate_id)) + } + None => (None, None), + }; + let connector_account_reference_id = item.content.transaction.gateway_account_id.clone(); + let transaction_created_at = item.content.transaction.date; + let status = enums::AttemptStatus::from(item.content.transaction.status); + let payment_method_type = + enums::PaymentMethod::from(item.content.transaction.payment_method); + let payment_method_details: ChargebeePaymentMethodDetails = + serde_json::from_str(&item.content.transaction.payment_method_details) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let payment_method_sub_type = + enums::PaymentMethodType::from(payment_method_details.card.funding_type); + Ok(Self { + amount, + currency, + merchant_reference_id, + connector_transaction_id, + error_code, + error_message, + processor_payment_method_token, + connector_customer_id, + connector_account_reference_id, + transaction_created_at, + status, + payment_method_type, + payment_method_sub_type, + }) + } +} + +impl From for enums::AttemptStatus { + fn from(status: ChargebeeTranasactionStatus) -> Self { + match status { + ChargebeeTranasactionStatus::InProgress + | ChargebeeTranasactionStatus::NeedsAttention => Self::Pending, + ChargebeeTranasactionStatus::Success => Self::Charged, + ChargebeeTranasactionStatus::Failure + | ChargebeeTranasactionStatus::Timeout + | ChargebeeTranasactionStatus::LateFailure => Self::Pending, + } + } +} + +impl From for enums::PaymentMethod { + fn from(payment_method: ChargebeeTransactionPaymentMethod) -> Self { + match payment_method { + ChargebeeTransactionPaymentMethod::Card => Self::Card, + } + } +} + +impl From for enums::PaymentMethodType { + fn from(funding_type: ChargebeeFundingType) -> Self { + match funding_type { + ChargebeeFundingType::Credit => Self::Credit, + ChargebeeFundingType::Debit => Self::Debit, + } + } +} +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +impl From for api_models::webhooks::IncomingWebhookEvent { + fn from(event: ChargebeeEventType) -> Self { + match event { + ChargebeeEventType::PaymentSucceeded => Self::RecoveryPaymentSuccess, + ChargebeeEventType::PaymentFailed => Self::RecoveryPaymentFailure, + ChargebeeEventType::InvoiceDeleted => Self::RecoveryInvoiceCancel, + } + } +} + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +impl TryFrom for revenue_recovery::RevenueRecoveryInvoiceData { + type Error = error_stack::Report; + fn try_from(item: ChargebeeInvoiceBody) -> Result { + let merchant_reference_id = + common_utils::id_type::PaymentReferenceId::from_str(&item.content.invoice.id) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(Self { + amount: item.content.invoice.total, + currency: item.content.invoice.currency_code, + merchant_reference_id, + }) + } +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index c3abbc649e..5d552ea4e6 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -38,7 +38,7 @@ v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] -revenue_recovery =["api_models/revenue_recovery","hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery"] +revenue_recovery =["api_models/revenue_recovery","hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery","hyperswitch_connectors/revenue_recovery"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 4d7cc1620f..38b1ddb0f8 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -14,6 +14,7 @@ use diesel_models::configs; #[cfg(all(any(feature = "v1", feature = "v2"), feature = "olap"))] use diesel_models::{business_profile::CardTestingGuardConfig, organization::OrganizationBridge}; use error_stack::{report, FutureExt, ResultExt}; +use hyperswitch_connectors::connectors::chargebee; use hyperswitch_domain_models::merchant_connector_account::{ FromRequestEncryptableMerchantConnectorAccount, UpdateEncryptableMerchantConnectorAccount, }; @@ -1310,6 +1311,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { cashtocode::transformers::CashtocodeAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Chargebee => { + chargebee::transformers::ChargebeeAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Checkout => { checkout::transformers::CheckoutAuthType::try_from(self.auth_type)?; Ok(()) @@ -1844,6 +1849,8 @@ impl ConnectorTypeAndConnectorName<'_> { api_enums::convert_authentication_connector(self.connector_name.to_string().as_str()); let tax_connector = api_enums::convert_tax_connector(self.connector_name.to_string().as_str()); + let billing_connector = + api_enums::convert_billing_connector(self.connector_name.to_string().as_str()); if pm_auth_connector.is_some() { if self.connector_type != &api_enums::ConnectorType::PaymentMethodAuth @@ -1868,6 +1875,13 @@ impl ConnectorTypeAndConnectorName<'_> { } .into()); } + } else if billing_connector.is_some() { + if self.connector_type != &api_enums::ConnectorType::BillingProcessor { + return Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector type given".to_string(), + } + .into()); + } } else { let routable_connector_option = self .connector_name diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index c5ca0e72ab..d4b9d1eafc 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -359,9 +359,11 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(connector::Braintree::new()))) } enums::Connector::Cashtocode => { - // enums::Connector::Chargebee => Ok(ConnectorEnum::Old(Box::new(connector::Chargebee))), Ok(ConnectorEnum::Old(Box::new(connector::Cashtocode::new()))) } + enums::Connector::Chargebee => { + Ok(ConnectorEnum::Old(Box::new(connector::Chargebee::new()))) + } enums::Connector::Checkout => { Ok(ConnectorEnum::Old(Box::new(connector::Checkout::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 0dd11a2f38..4da26061e4 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -225,7 +225,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Boku => Self::Boku, api_enums::Connector::Braintree => Self::Braintree, api_enums::Connector::Cashtocode => Self::Cashtocode, - // api_enums::Connector::Chargebee => Self::Chargebee, + api_enums::Connector::Chargebee => Self::Chargebee, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, api_enums::Connector::Coingate => Self::Coingate, diff --git a/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/down.sql b/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/down.sql new file mode 100644 index 0000000000..c7c9cbeb40 --- /dev/null +++ b/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/up.sql b/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/up.sql new file mode 100644 index 0000000000..ac668f0c1a --- /dev/null +++ b/v2_migrations/2025-02-24-053820_add_billing_processor_value_to_connector_type_enum/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TYPE "ConnectorType" ADD VALUE If NOT EXISTS 'billing_processor'; \ No newline at end of file