diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 3f54ca3ad6..1097811dfd 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -8593,6 +8593,33 @@ "unresolved" ] }, + "BHNGiftCardDetails": { + "type": "object", + "required": [ + "account_number", + "pin", + "cvv2", + "expiration_date" + ], + "properties": { + "account_number": { + "type": "string", + "description": "The gift card or account number" + }, + "pin": { + "type": "string", + "description": "The security PIN for gift cards requiring it" + }, + "cvv2": { + "type": "string", + "description": "The CVV2 code for Open Loop/VPLN products" + }, + "expiration_date": { + "type": "string", + "description": "The expiration date in MMYYYY format for Open Loop/VPLN products" + } + } + }, "BacsBankDebitAdditionalData": { "type": "object", "required": [ @@ -11814,6 +11841,7 @@ "billwerk", "bitpay", "bluesnap", + "blackhawknetwork", "bluecode", "boku", "braintree", @@ -15738,6 +15766,17 @@ "type": "object" } } + }, + { + "type": "object", + "required": [ + "bhn_card_network" + ], + "properties": { + "bhn_card_network": { + "type": "object" + } + } } ] }, @@ -15764,6 +15803,17 @@ "type": "object" } } + }, + { + "type": "object", + "required": [ + "bhn_card_network" + ], + "properties": { + "bhn_card_network": { + "$ref": "#/components/schemas/BHNGiftCardDetails" + } + } } ] }, @@ -22006,6 +22056,7 @@ "bni_va", "breadpay", "bri_va", + "bhn_card_network", "card_redirect", "cimb_va", "classic", @@ -29796,6 +29847,7 @@ "billwerk", "bitpay", "bambora", + "blackhawknetwork", "bamboraapac", "bluesnap", "bluecode", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index d212afa84a..5bf813777d 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -5054,6 +5054,33 @@ "unresolved" ] }, + "BHNGiftCardDetails": { + "type": "object", + "required": [ + "account_number", + "pin", + "cvv2", + "expiration_date" + ], + "properties": { + "account_number": { + "type": "string", + "description": "The gift card or account number" + }, + "pin": { + "type": "string", + "description": "The security PIN for gift cards requiring it" + }, + "cvv2": { + "type": "string", + "description": "The CVV2 code for Open Loop/VPLN products" + }, + "expiration_date": { + "type": "string", + "description": "The expiration date in MMYYYY format for Open Loop/VPLN products" + } + } + }, "BacsBankDebitAdditionalData": { "type": "object", "required": [ @@ -8358,6 +8385,7 @@ "billwerk", "bitpay", "bluesnap", + "blackhawknetwork", "bluecode", "boku", "braintree", @@ -11884,6 +11912,17 @@ "type": "object" } } + }, + { + "type": "object", + "required": [ + "bhn_card_network" + ], + "properties": { + "bhn_card_network": { + "type": "object" + } + } } ] }, @@ -11910,6 +11949,17 @@ "type": "object" } } + }, + { + "type": "object", + "required": [ + "bhn_card_network" + ], + "properties": { + "bhn_card_network": { + "$ref": "#/components/schemas/BHNGiftCardDetails" + } + } } ] }, @@ -18263,6 +18313,7 @@ "bni_va", "breadpay", "bri_va", + "bhn_card_network", "card_redirect", "cimb_va", "classic", @@ -23450,6 +23501,7 @@ "billwerk", "bitpay", "bambora", + "blackhawknetwork", "bamboraapac", "bluesnap", "bluecode", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 61ee499850..5660819cea 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3024,6 +3024,7 @@ impl GetPaymentMethodType for GiftCardData { match self { Self::Givex(_) => api_enums::PaymentMethodType::Givex, Self::PaySafeCard {} => api_enums::PaymentMethodType::PaySafeCard, + Self::BhnCardNetwork(_) => api_enums::PaymentMethodType::BhnCardNetwork, } } } @@ -3033,6 +3034,23 @@ impl GetPaymentMethodType for GiftCardData { pub enum GiftCardData { Givex(GiftCardDetails), PaySafeCard {}, + BhnCardNetwork(BHNGiftCardDetails), +} +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct BHNGiftCardDetails { + /// The gift card or account number + #[schema(value_type = String)] + pub account_number: Secret, + /// The security PIN for gift cards requiring it + #[schema(value_type = String)] + pub pin: Option>, + /// The CVV2 code for Open Loop/VPLN products + #[schema(value_type = String)] + pub cvv2: Option>, + /// The expiration date in MMYYYY format for Open Loop/VPLN products + #[schema(value_type = String)] + pub expiration_date: Option, } #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, ToSchema, Eq, PartialEq)] diff --git a/crates/api_models/src/payments/additional_info.rs b/crates/api_models/src/payments/additional_info.rs index de7493cafc..d580a28827 100644 --- a/crates/api_models/src/payments/additional_info.rs +++ b/crates/api_models/src/payments/additional_info.rs @@ -200,6 +200,7 @@ pub struct LocalBankTransferAdditionalData { pub enum GiftCardAdditionalData { Givex(Box), PaySafeCard {}, + BhnCardNetwork {}, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index cb0e4462f9..9d73bc6bc9 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -70,6 +70,7 @@ pub enum RoutableConnectors { Billwerk, Bitpay, Bambora, + Blackhawknetwork, Bamboraapac, Bluesnap, Bluecode, @@ -239,6 +240,7 @@ pub enum Connector { Billwerk, Bitpay, Bluesnap, + Blackhawknetwork, Bluecode, Boku, Braintree, @@ -436,6 +438,7 @@ impl Connector { | Self::Billwerk | Self::Bitpay | Self::Bluesnap + | Self::Blackhawknetwork | Self::Bluecode | Self::Boku | Self::Braintree @@ -608,6 +611,7 @@ impl From for Connector { RoutableConnectors::Bambora => Self::Bambora, RoutableConnectors::Bamboraapac => Self::Bamboraapac, RoutableConnectors::Bluesnap => Self::Bluesnap, + RoutableConnectors::Blackhawknetwork => Self::Blackhawknetwork, RoutableConnectors::Bluecode => Self::Bluecode, RoutableConnectors::Boku => Self::Boku, RoutableConnectors::Braintree => Self::Braintree, @@ -738,6 +742,7 @@ impl TryFrom for RoutableConnectors { Connector::Bambora => Ok(Self::Bambora), Connector::Bamboraapac => Ok(Self::Bamboraapac), Connector::Bluesnap => Ok(Self::Bluesnap), + Connector::Blackhawknetwork => Ok(Self::Blackhawknetwork), Connector::Bluecode => Ok(Self::Bluecode), Connector::Boku => Ok(Self::Boku), Connector::Braintree => Ok(Self::Braintree), diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 22091eca89..0791621426 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1966,6 +1966,7 @@ pub enum PaymentMethodType { BniVa, Breadpay, BriVa, + BhnCardNetwork, #[cfg(feature = "v2")] Card, CardRedirect, @@ -2089,6 +2090,7 @@ impl PaymentMethodType { Self::BniVa => "BNI Virtual Account", Self::Breadpay => "Breadpay", Self::BriVa => "BRI Virtual Account", + Self::BhnCardNetwork => "BHN Card Network", Self::CardRedirect => "Card Redirect", Self::CimbVa => "CIMB Virtual Account", Self::ClassicReward => "Classic Reward", diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index f897c8c24b..a2039e6290 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -1892,6 +1892,7 @@ impl From for PaymentMethod { PaymentMethodType::RedCompra => Self::Voucher, PaymentMethodType::RedPagos => Self::Voucher, PaymentMethodType::Cashapp => Self::Wallet, + PaymentMethodType::BhnCardNetwork => Self::GiftCard, PaymentMethodType::Givex => Self::GiftCard, PaymentMethodType::Oxxo => Self::Voucher, PaymentMethodType::OpenBankingUk => Self::BankRedirect, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 63fb9640e2..7a77a24198 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -409,6 +409,7 @@ impl ConnectorConfig { Connector::Bitpay => Ok(connector_data.bitpay), Connector::Bluesnap => Ok(connector_data.bluesnap), Connector::Bluecode => Ok(connector_data.bluecode), + Connector::Blackhawknetwork => Ok(connector_data.blackhawknetwork), Connector::Boku => Ok(connector_data.boku), Connector::Braintree => Ok(connector_data.braintree), Connector::Breadpay => Ok(connector_data.breadpay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 160a29aa1a..5904ee6d73 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -6571,6 +6571,14 @@ placeholder="Enter the merchant name" required=true type="Text" +[blackhawknetwork] +[[blackhawknetwork.gift_card]] + payment_method_type = "bhn_card_network" +[blackhawknetwork.connector_auth.SignatureKey] +api_key="Client Id" +api_secret="Client Secret" +key1="Product Line Id" + [payload] [[payload.credit]] payment_method_type = "AmericanExpress" @@ -6616,10 +6624,6 @@ merchant_secret="Source verification key" [affirm.connector_auth.HeaderKey] api_key = "API Key" -[blackhawknetwork] -[blackhawknetwork.connector_auth.HeaderKey] -api_key = "API Key" - [trustpayments] [trustpayments.connector_auth.HeaderKey] api_key = "API Key" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 47aff66d84..c3d6fe5ce7 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -5225,6 +5225,14 @@ placeholder = "Enter the merchant name" required = true type = "Text" +[blackhawknetwork] +[[blackhawknetwork.gift_card]] + payment_method_type = "bhn_card_network" +[blackhawknetwork.connector_auth.SignatureKey] +api_key="Client Id" +api_secret="Client Secret" +key1="Product Line Id" + [payload] [[payload.credit]] payment_method_type = "AmericanExpress" @@ -5274,9 +5282,6 @@ api_key = "API Key" [trustpayments.connector_auth.HeaderKey] api_key = "API Key" -[blackhawknetwork] -[blackhawknetwork.connector_auth.HeaderKey] -api_key = "API Key" [flexiti] [flexiti.connector_auth.BodyKey] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index d1ea6ba609..77cbac84a0 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -6554,6 +6554,14 @@ placeholder = "Enter the merchant name" required = true type = "Text" +[blackhawknetwork] +[[blackhawknetwork.gift_card]] + payment_method_type = "bhn_card_network" +[blackhawknetwork.connector_auth.SignatureKey] +api_key="Client Id" +api_secret="Client Secret" +key1="Product Line Id" + [payload] [[payload.credit]] payment_method_type = "AmericanExpress" @@ -6603,9 +6611,6 @@ api_key = "API Key" [trustpayments.connector_auth.HeaderKey] api_key = "API Key" -[blackhawknetwork] -[blackhawknetwork.connector_auth.HeaderKey] -api_key = "API Key" [breadpay] [breadpay.connector_auth.BodyKey] diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 0cc056a963..5eeeeb6710 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -247,6 +247,7 @@ pub enum BankTransferType { pub enum GiftCardType { PaySafeCard, Givex, + BhnCardNetwork, } #[derive( diff --git a/crates/euclid/src/frontend/dir/lowering.rs b/crates/euclid/src/frontend/dir/lowering.rs index b2853fbd53..113a28680f 100644 --- a/crates/euclid/src/frontend/dir/lowering.rs +++ b/crates/euclid/src/frontend/dir/lowering.rs @@ -142,6 +142,7 @@ impl From for global_enums::PaymentMethodType { match value { enums::GiftCardType::PaySafeCard => Self::PaySafeCard, enums::GiftCardType::Givex => Self::Givex, + enums::GiftCardType::BhnCardNetwork => Self::BhnCardNetwork, } } } diff --git a/crates/euclid/src/frontend/dir/transformers.rs b/crates/euclid/src/frontend/dir/transformers.rs index 0f0e8b96aa..28e4988594 100644 --- a/crates/euclid/src/frontend/dir/transformers.rs +++ b/crates/euclid/src/frontend/dir/transformers.rs @@ -204,6 +204,9 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet global_enums::PaymentMethodType::IndonesianBankTransfer => { Ok(dirval!(BankTransferType = IndonesianBankTransfer)) } + global_enums::PaymentMethodType::BhnCardNetwork => { + Ok(dirval!(GiftCardType = BhnCardNetwork)) + } } } } diff --git a/crates/hyperswitch_connectors/src/connectors/adyen.rs b/crates/hyperswitch_connectors/src/connectors/adyen.rs index fffef7fd41..47f2eefeaf 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen.rs @@ -311,6 +311,7 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::CardRedirect | PaymentMethodType::DirectCarrierBilling | PaymentMethodType::Fps + | PaymentMethodType::BhnCardNetwork | PaymentMethodType::DuitNow | PaymentMethodType::Interac | PaymentMethodType::Multibanco diff --git a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs index 63c1b0d8a3..76a13085ea 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs @@ -1780,10 +1780,12 @@ impl TryFrom<&PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'_> { balance_pm, ))) } - GiftCardData::PaySafeCard {} => Err(errors::ConnectorError::FlowNotSupported { - flow: "Balance".to_string(), - connector: "adyen".to_string(), - }), + GiftCardData::PaySafeCard {} | GiftCardData::BhnCardNetwork(_) => { + Err(errors::ConnectorError::FlowNotSupported { + flow: "Balance".to_string(), + connector: "adyen".to_string(), + }) + } }, _ => Err(errors::ConnectorError::FlowNotSupported { flow: "Balance".to_string(), @@ -2130,6 +2132,10 @@ impl TryFrom<&GiftCardData> for AdyenPaymentMethod<'_> { }; Ok(AdyenPaymentMethod::AdyenGiftCard(Box::new(gift_card_pm))) } + GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Adyen"), + ) + .into()), } } } diff --git a/crates/hyperswitch_connectors/src/connectors/blackhawknetwork.rs b/crates/hyperswitch_connectors/src/connectors/blackhawknetwork.rs index 60ebf16f77..a6c12d16fe 100644 --- a/crates/hyperswitch_connectors/src/connectors/blackhawknetwork.rs +++ b/crates/hyperswitch_connectors/src/connectors/blackhawknetwork.rs @@ -1,13 +1,12 @@ pub mod transformers; - use std::sync::LazyLock; -use common_enums::enums; +use common_enums::{enums, enums::PaymentMethodType}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -15,20 +14,24 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{ - ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -37,25 +40,26 @@ use hyperswitch_interfaces::{ ConnectorValidation, }, configs::Connectors, + consts::NO_ERROR_MESSAGE, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::Maskable; use transformers as blackhawknetwork; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Blackhawknetwork { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Blackhawknetwork { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -72,6 +76,92 @@ impl api::Refund for Blackhawknetwork {} impl api::RefundExecute for Blackhawknetwork {} impl api::RefundSync for Blackhawknetwork {} impl api::PaymentToken for Blackhawknetwork {} +impl api::PaymentsPreProcessing for Blackhawknetwork {} + +impl ConnectorIntegration + for Blackhawknetwork +{ + fn get_headers( + &self, + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + "application/x-www-form-urlencoded".to_string().into(), + )]) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}/realms/dev-experience/protocol/openid-connect/token", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let auth = blackhawknetwork::BlackhawknetworkAuthType::try_from(&req.connector_auth_type)?; + let connector_req = blackhawknetwork::BlackhawknetworkAccessTokenRequest { + grant_type: "client_credentials".to_string(), + client_id: auth.client_id.clone(), + client_secret: auth.client_secret.clone(), + scope: "openid".to_string(), + }; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) + .set_body(types::RefreshTokenType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: blackhawknetwork::BlackhawknetworkTokenResponse = res + .response + .parse_struct("BlackhawknetworkTokenResponse") + .or_else(|_| res.response.parse_struct("BlackhawknetworkErrorResponse")) + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } +} impl ConnectorIntegration for Blackhawknetwork @@ -87,7 +177,7 @@ where &self, req: &RouterData, _connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -104,10 +194,7 @@ impl ConnectorCommon for Blackhawknetwork { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Minor - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { @@ -120,14 +207,9 @@ impl ConnectorCommon for Blackhawknetwork { fn get_auth_header( &self, - auth_type: &ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = blackhawknetwork::BlackhawknetworkAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![]) } fn build_error_response( @@ -145,9 +227,12 @@ impl ConnectorCommon for Blackhawknetwork { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.error, + message: response + .error_description + .clone() + .unwrap_or(NO_ERROR_MESSAGE.to_owned()), + reason: response.error_description, attempt_status: None, connector_transaction_id: None, network_advice_code: None, @@ -161,7 +246,7 @@ impl ConnectorCommon for Blackhawknetwork { impl ConnectorValidation for Blackhawknetwork { fn validate_mandate_payment( &self, - _pm_type: Option, + _pm_type: Option, pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { match pm_data { @@ -187,38 +272,112 @@ impl ConnectorValidation for Blackhawknetwork { impl ConnectorIntegration for Blackhawknetwork { //TODO: implement sessions flow } - -impl ConnectorIntegration +impl ConnectorIntegration for Blackhawknetwork { + fn get_headers( + &self, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut headers = vec![( + headers::CONTENT_TYPE.to_string(), + "application/x-www-form-urlencoded".to_string().into(), + )]; + let mut auth_header = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut auth_header); + Ok(headers) + } + + fn get_url( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult { + let base_url = self.base_url(connectors); + let connector_req = blackhawknetwork::BlackhawknetworkVerifyAccountRequest::try_from(req)?; + let query = serde_urlencoded::to_string(&connector_req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(format!( + "{base_url}/accountProcessing/v1/verifyAccount?{query}" + )) + } + + fn build_request( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsPreProcessingRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: blackhawknetwork::BlackhawknetworkVerifyAccountResponse = res + .response + .parse_struct("BlackhawknetworkVerifyAccountResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } } impl ConnectorIntegration for Blackhawknetwork { } - impl ConnectorIntegration for Blackhawknetwork { fn get_headers( &self, req: &PaymentsAuthorizeRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut headers = vec![( + headers::CONTENT_TYPE.to_string(), + "application/json".to_string().into(), + )]; + let mut auth_header = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut auth_header); + Ok(headers) } fn get_content_type(&self) -> &'static str { - self.common_get_content_type() + "application/json" } fn get_url( &self, _req: &PaymentsAuthorizeRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}/accountProcessing/v1/redeem", + self.base_url(connectors) + )) } fn get_request_body( @@ -231,7 +390,6 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: blackhawknetwork::BlackhawknetworkPaymentsResponse = res + let response: blackhawknetwork::BlackhawknetworkRedeemResponse = res .response - .parse_struct("Blackhawknetwork PaymentsAuthorizeResponse") + .parse_struct("BlackhawknetworkRedeemResponse") + .or_else(|_| res.response.parse_struct("BlackhawknetworkErrorResponse")) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { response, data: data.clone(), @@ -285,303 +446,85 @@ impl ConnectorIntegration, ) -> CustomResult { - self.build_error_response(res, event_builder) + let response: blackhawknetwork::BlackhawknetworkErrorResponse = res + .response + .parse_struct("BlackhawknetworkErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error, + message: response + .error_description + .unwrap_or(NO_ERROR_MESSAGE.to_owned()), + reason: Some("Verify redemption details or contact BHN support".to_string()), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + connector_metadata: None, + }) } } impl ConnectorIntegration for Blackhawknetwork { - fn get_headers( - &self, - req: &PaymentsSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &PaymentsSyncRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &PaymentsSyncRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: blackhawknetwork::BlackhawknetworkPaymentsResponse = res - .response - .parse_struct("blackhawknetwork PaymentsSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Payments Sync".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) } } impl ConnectorIntegration for Blackhawknetwork { - fn get_headers( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) - } - fn build_request( &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: blackhawknetwork::BlackhawknetworkPaymentsResponse = res - .response - .parse_struct("Blackhawknetwork PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Capture".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) } } impl ConnectorIntegration for Blackhawknetwork {} impl ConnectorIntegration for Blackhawknetwork { - fn get_headers( - &self, - req: &RefundsRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundsRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - req: &RefundsRouterData, - _connectors: &Connectors, - ) -> CustomResult { - let refund_amount = utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; - - let connector_router_data = - blackhawknetwork::BlackhawknetworkRouterData::from((refund_amount, req)); - let connector_req = - blackhawknetwork::BlackhawknetworkRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn build_request( - &self, - req: &RefundsRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - let request = RequestBuilder::new() - .method(Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - let response: blackhawknetwork::RefundResponse = res - .response - .parse_struct("blackhawknetwork RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Refunds".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) } } impl ConnectorIntegration for Blackhawknetwork { - fn get_headers( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundSyncRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: blackhawknetwork::RefundResponse = res - .response - .parse_struct("blackhawknetwork RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Refunds Sync".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) } } @@ -609,9 +552,6 @@ impl webhooks::IncomingWebhook for Blackhawknetwork { } } -static BLACKHAWKNETWORK_SUPPORTED_PAYMENT_METHODS: LazyLock = - LazyLock::new(SupportedPaymentMethods::new); - static BLACKHAWKNETWORK_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { display_name: "Blackhawknetwork", description: "Blackhawknetwork connector", @@ -621,6 +561,25 @@ static BLACKHAWKNETWORK_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { static BLACKHAWKNETWORK_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; +static BLACKHAWKNETWORK_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; + let mut supported_payment_methods = SupportedPaymentMethods::new(); + + supported_payment_methods.add( + enums::PaymentMethod::GiftCard, + PaymentMethodType::BhnCardNetwork, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::NotSupported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: None, + }, + ); + + supported_payment_methods + }); + impl ConnectorSpecifications for Blackhawknetwork { fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { Some(&BLACKHAWKNETWORK_CONNECTOR_INFO) diff --git a/crates/hyperswitch_connectors/src/connectors/blackhawknetwork/transformers.rs b/crates/hyperswitch_connectors/src/connectors/blackhawknetwork/transformers.rs index e56a1f02bd..4df04a536d 100644 --- a/crates/hyperswitch_connectors/src/connectors/blackhawknetwork/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/blackhawknetwork/transformers.rs @@ -1,28 +1,29 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_enums::{enums, Currency}; +use common_utils::types::{MinorUnit, StringMajorUnit}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + payment_method_data::{GiftCardData, PaymentMethodData}, + router_data::{ + AccessToken, ConnectorAuthType, ErrorResponse, PaymentMethodBalance, RouterData, + }, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::{PaymentsPreProcessingData, ResponseId}, + router_response_types::{PaymentsResponseData, PreprocessingResponseId, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData}, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{consts::NO_ERROR_MESSAGE, errors}; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::types::{RefundsResponseRouterData, ResponseRouterData}; -//TODO: Fill the struct with respective fields pub struct BlackhawknetworkRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for BlackhawknetworkRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for BlackhawknetworkRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -30,114 +31,262 @@ impl From<(StringMinorUnit, T)> for BlackhawknetworkRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct BlackhawknetworkPaymentsRequest { - amount: StringMinorUnit, - card: BlackhawknetworkCard, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct BlackhawknetworkCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl TryFrom<&BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>> - for BlackhawknetworkPaymentsRequest -{ - type Error = error_stack::Report; - fn try_from( - item: &BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>, - ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( - "Card payment method not implemented".to_string(), - ) - .into()), - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), - } - } -} - -//TODO: Fill the struct with respective fields -// Auth Struct +#[derive(Debug, Clone)] pub struct BlackhawknetworkAuthType { - pub(super) api_key: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, + pub(super) product_line_id: Secret, } impl TryFrom<&ConnectorAuthType> for BlackhawknetworkAuthType { type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + client_id: api_key.clone(), + client_secret: api_secret.clone(), + product_line_id: key1.clone(), }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), - } - } -} -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum BlackhawknetworkPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for common_enums::AttemptStatus { - fn from(item: BlackhawknetworkPaymentStatus) -> Self { - match item { - BlackhawknetworkPaymentStatus::Succeeded => Self::Charged, - BlackhawknetworkPaymentStatus::Failed => Self::Failure, - BlackhawknetworkPaymentStatus::Processing => Self::Authorizing, + _ => Err(errors::ConnectorError::FailedToObtainAuthType) + .attach_printable("Unsupported authentication type for Blackhawk Network"), } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BlackhawknetworkPaymentsResponse { - status: BlackhawknetworkPaymentStatus, - id: String, +#[derive(Debug, Serialize, Deserialize)] +pub struct BlackhawknetworkAccessTokenRequest { + pub grant_type: String, + pub client_id: Secret, + pub client_secret: Secret, + pub scope: String, } - -impl TryFrom> - for RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charges: None, + response: Ok(AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, }), ..item.data }) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlackhawknetworkVerifyAccountRequest { + pub account_number: Secret, + pub product_line_id: Secret, + pub account_type: AccountType, + #[serde(skip_serializing_if = "Option::is_none")] + pub pin: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cvv2: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub expiration_date: Option>, +} +impl TryFrom<&PaymentsPreProcessingRouterData> for BlackhawknetworkVerifyAccountRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsPreProcessingRouterData) -> Result { + let auth = BlackhawknetworkAuthType::try_from(&item.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + + let gift_card_data = match &item.request.payment_method_data { + Some(PaymentMethodData::GiftCard(gc)) => match gc.as_ref() { + GiftCardData::BhnCardNetwork(data) => data, + _ => { + return Err(errors::ConnectorError::FlowNotSupported { + flow: "Balance".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) + } + }, + _ => { + return Err(errors::ConnectorError::FlowNotSupported { + flow: "Balance".to_string(), + connector: "BlackHawkNetwork".to_string(), + } + .into()) + } + }; + + Ok(Self { + account_number: gift_card_data.account_number.clone(), + product_line_id: auth.product_line_id, + account_type: AccountType::GiftCard, + pin: gift_card_data.pin.clone(), + cvv2: gift_card_data.cvv2.clone(), + expiration_date: gift_card_data.expiration_date.clone().map(Secret::new), + }) + } +} + +impl + TryFrom< + ResponseRouterData< + F, + BlackhawknetworkVerifyAccountResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + BlackhawknetworkVerifyAccountResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(PaymentsResponseData::PreProcessingResponse { + pre_processing_id: PreprocessingResponseId::PreProcessingId( + item.response.account.entity_id, + ), + connector_metadata: None, + session_token: None, + connector_response_reference_id: None, + }), + payment_method_balance: Some(PaymentMethodBalance { + currency: item.response.account.currency, + amount: item.response.account.balance, + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BlackhawknetworkTokenResponse { + pub access_token: Secret, + pub expires_in: i64, +} +#[derive(Serialize, Debug)] +pub struct BlackhawknetworkPaymentsRequest { + pub account_id: String, + pub amount: StringMajorUnit, + pub currency: Currency, +} + +impl TryFrom<&BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>> + for BlackhawknetworkPaymentsRequest +{ + type Error = error_stack::Report; + + fn try_from( + item: &BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match &item.router_data.request.payment_method_data { + PaymentMethodData::GiftCard(_gift_card) => { + let account_id = item + .router_data + .preprocessing_id + .to_owned() + .ok_or_else(|| { + errors::ConnectorError::MissingConnectorRelatedTransactionID { + id: "entity_id".to_string(), + } + })?; + + Ok(Self { + account_id, + amount: item.amount.clone(), + currency: item.router_data.request.currency, + }) + } + _ => Err(error_stack::Report::new( + errors::ConnectorError::NotImplemented("Non-gift card payment method".to_string()), + )), + } + } +} +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BlackhawknetworkRedeemResponse { + Success(BlackhawknetworkPaymentsResponse), + Error(BlackhawknetworkErrorResponse), +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlackhawknetworkPaymentsResponse { + pub id: String, + #[serde(rename = "transactionStatus")] + pub status: BlackhawknetworkAttemptStatus, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum BlackhawknetworkAttemptStatus { + Approved, + Declined, + Pending, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: ResponseRouterData, + ) -> Result { + match item.response { + BlackhawknetworkRedeemResponse::Success(response) => Ok(Self { + status: match response.status { + BlackhawknetworkAttemptStatus::Approved => enums::AttemptStatus::Charged, + BlackhawknetworkAttemptStatus::Declined => enums::AttemptStatus::Failure, + BlackhawknetworkAttemptStatus::Pending => enums::AttemptStatus::Pending, + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }), + BlackhawknetworkRedeemResponse::Error(error_response) => Ok(Self { + response: Err(ErrorResponse { + status_code: item.http_code, + code: error_response.error.clone(), + message: error_response + .error_description + .clone() + .unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: error_response.error_description, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + connector_metadata: None, + }), + ..item.data + }), + } + } +} + #[derive(Default, Debug, Serialize)] pub struct BlackhawknetworkRefundRequest { - pub amount: StringMinorUnit, + pub amount: StringMajorUnit, } impl TryFrom<&BlackhawknetworkRouterData<&RefundsRouterData>> @@ -153,8 +302,6 @@ impl TryFrom<&BlackhawknetworkRouterData<&RefundsRouterData>> } } -// Type definition for Refund Response - #[allow(dead_code)] #[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] pub enum RefundStatus { @@ -212,14 +359,49 @@ impl TryFrom> for RefundsRouter } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct BlackhawknetworkErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, - pub network_advice_code: Option, - pub network_decline_code: Option, - pub network_error_message: Option, +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AccountType { + CreditCard, + GiftCard, + LoyaltyCard, + PhoneCard, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum AccountStatus { + New, + Activated, + Closed, +} + +impl From for common_enums::AttemptStatus { + fn from(item: AccountStatus) -> Self { + match item { + AccountStatus::New | AccountStatus::Activated => Self::Pending, + AccountStatus::Closed => Self::Failure, + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct AccountInformation { + pub entity_id: String, + pub balance: MinorUnit, + pub currency: Currency, + pub status: AccountStatus, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct BlackhawknetworkVerifyAccountResponse { + account: AccountInformation, +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlackhawknetworkErrorResponse { + pub error: String, + pub error_description: Option, } diff --git a/crates/hyperswitch_connectors/src/connectors/klarna.rs b/crates/hyperswitch_connectors/src/connectors/klarna.rs index b106576bc6..6b053b078a 100644 --- a/crates/hyperswitch_connectors/src/connectors/klarna.rs +++ b/crates/hyperswitch_connectors/src/connectors/klarna.rs @@ -552,6 +552,7 @@ impl ConnectorIntegration> for PaypalPayments | enums::PaymentMethodType::Becs | enums::PaymentMethodType::Benefit | enums::PaymentMethodType::Bizum + | enums::PaymentMethodType::BhnCardNetwork | enums::PaymentMethodType::Blik | enums::PaymentMethodType::Boleto | enums::PaymentMethodType::BcaBankTransfer @@ -1410,12 +1411,12 @@ impl TryFrom<&GiftCardData> for PaypalPaymentsRequest { type Error = error_stack::Report; fn try_from(value: &GiftCardData) -> Result { match value { - GiftCardData::Givex(_) | GiftCardData::PaySafeCard {} => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Paypal"), - ) - .into()) - } + GiftCardData::Givex(_) + | GiftCardData::PaySafeCard {} + | GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Paypal"), + ) + .into()), } } } diff --git a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs index e4c7b708b6..159ea3ab09 100644 --- a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs @@ -529,12 +529,12 @@ impl TryFrom<&GiftCardData> for Shift4PaymentMethod { type Error = Error; fn try_from(gift_card_data: &GiftCardData) -> Result { match gift_card_data { - GiftCardData::Givex(_) | GiftCardData::PaySafeCard {} => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Shift4"), - ) - .into()) - } + GiftCardData::Givex(_) + | GiftCardData::PaySafeCard {} + | GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Shift4"), + ) + .into()), } } } diff --git a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs index 0e1f87b8a6..8feddc5c2b 100644 --- a/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stripe/transformers.rs @@ -794,6 +794,7 @@ impl TryFrom for StripePaymentMethodType { | enums::PaymentMethodType::Multibanco | enums::PaymentMethodType::OnlineBankingFpx | enums::PaymentMethodType::Paypal + | enums::PaymentMethodType::BhnCardNetwork | enums::PaymentMethodType::Pix | enums::PaymentMethodType::UpiCollect | enums::PaymentMethodType::UpiIntent @@ -1416,12 +1417,12 @@ fn create_stripe_payment_method( .into()), PaymentMethodData::GiftCard(giftcard_data) => match giftcard_data.deref() { - GiftCardData::Givex(_) | GiftCardData::PaySafeCard {} => Err( - ConnectorError::NotImplemented(get_unimplemented_payment_method_error_message( - "stripe", - )) - .into(), - ), + GiftCardData::Givex(_) + | GiftCardData::PaySafeCard {} + | GiftCardData::BhnCardNetwork(_) => Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("stripe"), + ) + .into()), }, PaymentMethodData::CardRedirect(cardredirect_data) => match cardredirect_data { CardRedirectData::Knet {} diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index 100237abae..3d94da666b 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -810,12 +810,12 @@ impl TryFrom<&GiftCardData> for ZenPaymentsRequest { type Error = error_stack::Report; fn try_from(value: &GiftCardData) -> Result { match value { - GiftCardData::PaySafeCard {} | GiftCardData::Givex(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Zen"), - ) - .into()) - } + GiftCardData::PaySafeCard {} + | GiftCardData::Givex(_) + | GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Zen"), + ) + .into()), } } } diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 7d746072f6..64c30a937f 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -1669,7 +1669,6 @@ default_imp_for_pre_processing_steps!( connectors::Bankofamerica, connectors::Billwerk, connectors::Bitpay, - connectors::Blackhawknetwork, connectors::Bluecode, connectors::Bluesnap, connectors::Braintree, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 1500da7656..aefb4f53f7 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -5539,6 +5539,7 @@ pub enum PaymentMethodDataType { PermataBankTransfer, BcaBankTransfer, BniVaBankTransfer, + BhnCardNetwork, BriVaBankTransfer, CimbVaBankTransfer, DanamonVaBankTransfer, @@ -5775,6 +5776,7 @@ impl From for PaymentMethodDataType { PaymentMethodData::GiftCard(gift_card_data) => match *gift_card_data { payment_method_data::GiftCardData::Givex(_) => Self::Givex, payment_method_data::GiftCardData::PaySafeCard {} => Self::PaySafeCar, + payment_method_data::GiftCardData::BhnCardNetwork(_) => Self::BhnCardNetwork, }, PaymentMethodData::CardToken(_) => Self::CardToken, PaymentMethodData::OpenBanking(data) => match data { diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 139889daf4..37812cf64e 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -672,6 +672,7 @@ pub struct JCSVoucherData {} pub enum GiftCardData { Givex(GiftCardDetails), PaySafeCard {}, + BhnCardNetwork(BHNGiftCardDetails), } #[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] @@ -683,6 +684,19 @@ pub struct GiftCardDetails { pub cvc: Secret, } +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct BHNGiftCardDetails { + /// The gift card or account number + pub account_number: Secret, + /// The security PIN for gift cards requiring it + pub pin: Option>, + /// The CVV2 code for Open Loop/VPLN products + pub cvv2: Option>, + /// The expiration date in MMYYYY format for Open Loop/VPLN products + pub expiration_date: Option, +} + #[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, Default)] #[serde(rename_all = "snake_case")] pub struct CardToken { @@ -1545,6 +1559,14 @@ impl From for GiftCardData { cvc: details.cvc, }), api_models::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, + api_models::payments::GiftCardData::BhnCardNetwork(details) => { + Self::BhnCardNetwork(BHNGiftCardDetails { + account_number: details.account_number, + pin: details.pin, + cvv2: details.cvv2, + expiration_date: details.expiration_date, + }) + } } } } @@ -1568,6 +1590,7 @@ impl From for payment_additional_types::GiftCardAdditionalData { }, )), GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, + GiftCardData::BhnCardNetwork(_) => Self::BhnCardNetwork {}, } } } @@ -2138,6 +2161,7 @@ impl GetPaymentMethodType for GiftCardData { match self { Self::Givex(_) => api_enums::PaymentMethodType::Givex, Self::PaySafeCard {} => api_enums::PaymentMethodType::PaySafeCard, + Self::BhnCardNetwork(_) => api_enums::PaymentMethodType::BhnCardNetwork, } } } diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 387fec7aeb..b33fdde309 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -152,6 +152,7 @@ fn get_dir_value_payment_method( api_enums::PaymentMethodType::Seicomart => Ok(dirval!(VoucherType = Seicomart)), api_enums::PaymentMethodType::PayEasy => Ok(dirval!(VoucherType = PayEasy)), api_enums::PaymentMethodType::Givex => Ok(dirval!(GiftCardType = Givex)), + api_enums::PaymentMethodType::BhnCardNetwork => Ok(dirval!(GiftCardType = BhnCardNetwork)), api_enums::PaymentMethodType::Benefit => Ok(dirval!(CardRedirectType = Benefit)), api_enums::PaymentMethodType::Knet => Ok(dirval!(CardRedirectType = Knet)), api_enums::PaymentMethodType::OpenBankingUk => { diff --git a/crates/kgraph_utils/src/transformers.rs b/crates/kgraph_utils/src/transformers.rs index fb41de208b..66bf4879ac 100644 --- a/crates/kgraph_utils/src/transformers.rs +++ b/crates/kgraph_utils/src/transformers.rs @@ -294,6 +294,9 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { api_enums::PaymentMethodType::Seicomart => Ok(dirval!(VoucherType = Seicomart)), api_enums::PaymentMethodType::PayEasy => Ok(dirval!(VoucherType = PayEasy)), api_enums::PaymentMethodType::Givex => Ok(dirval!(GiftCardType = Givex)), + api_enums::PaymentMethodType::BhnCardNetwork => { + Ok(dirval!(GiftCardType = BhnCardNetwork)) + } api_enums::PaymentMethodType::Benefit => Ok(dirval!(CardRedirectType = Benefit)), api_enums::PaymentMethodType::Knet => Ok(dirval!(CardRedirectType = Knet)), api_enums::PaymentMethodType::OpenBankingUk => { diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 16ce1cf862..b5486ac78c 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -633,6 +633,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CustomerDetails, api_models::payments::GiftCardData, api_models::payments::GiftCardDetails, + api_models::payments::BHNGiftCardDetails, api_models::payments::MobilePaymentData, api_models::payments::MobilePaymentResponse, api_models::payments::Address, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 2751055a53..1c52daa639 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -608,6 +608,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CustomerDetails, api_models::payments::GiftCardData, api_models::payments::GiftCardDetails, + api_models::payments::BHNGiftCardDetails, api_models::payments::MobilePaymentData, api_models::payments::MobilePaymentResponse, api_models::payments::Address, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index efad0c8e23..bead557736 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2586,6 +2586,7 @@ pub enum PaymentMethodDataType { Seicomart, PayEasy, Givex, + BhnCardNetwork, PaySafeCar, CardToken, LocalBankTransfer, @@ -2800,6 +2801,7 @@ impl From for PaymentMethodDataType { domain::payments::PaymentMethodData::GiftCard(gift_card_data) => { match *gift_card_data { domain::payments::GiftCardData::Givex(_) => Self::Givex, + domain::payments::GiftCardData::BhnCardNetwork(_)=>Self::BhnCardNetwork, domain::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCar, } } diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index cb91889017..a0665a4f9e 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -129,6 +129,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { bluesnap::transformers::BluesnapAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Blackhawknetwork => { + blackhawknetwork::transformers::BlackhawknetworkAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Bluecode => { bluecode::transformers::BluecodeAuthType::try_from(self.auth_type)?; bluecode::transformers::BluecodeMetadataObject::try_from(self.connector_meta_data)?; diff --git a/crates/router/src/types/api/connector_mapping.rs b/crates/router/src/types/api/connector_mapping.rs index 29f14f7943..1886804229 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -144,6 +144,9 @@ impl ConnectorData { enums::Connector::Bitpay => { Ok(ConnectorEnum::Old(Box::new(connector::Bitpay::new()))) } + enums::Connector::Blackhawknetwork => Ok(ConnectorEnum::Old(Box::new( + connector::Blackhawknetwork::new(), + ))), enums::Connector::Bluesnap => { Ok(ConnectorEnum::Old(Box::new(connector::Bluesnap::new()))) } diff --git a/crates/router/src/types/api/feature_matrix.rs b/crates/router/src/types/api/feature_matrix.rs index d7307e7ab6..8d5ff7d4f1 100644 --- a/crates/router/src/types/api/feature_matrix.rs +++ b/crates/router/src/types/api/feature_matrix.rs @@ -58,6 +58,9 @@ impl FeatureMatrixConnectorData { enums::Connector::Bitpay => { Ok(ConnectorEnum::Old(Box::new(connector::Bitpay::new()))) } + enums::Connector::Blackhawknetwork => Ok(ConnectorEnum::Old(Box::new( + connector::Blackhawknetwork::new(), + ))), enums::Connector::Bluesnap => { Ok(ConnectorEnum::Old(Box::new(connector::Bluesnap::new()))) } diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index cf29d04296..9d7c568cf3 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -22,6 +22,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Billwerk => Self::Billwerk, api_enums::Connector::Bitpay => Self::Bitpay, api_enums::Connector::Bluesnap => Self::Bluesnap, + api_enums::Connector::Blackhawknetwork => Self::Blackhawknetwork, api_enums::Connector::Bluecode => Self::Bluecode, api_enums::Connector::Boku => Self::Boku, api_enums::Connector::Braintree => Self::Braintree, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 8109df0755..cc7fd65a41 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -368,9 +368,9 @@ impl ForeignFrom for api_enums::PaymentMethod { | api_enums::PaymentMethodType::SepaBankTransfer | api_enums::PaymentMethodType::IndonesianBankTransfer | api_enums::PaymentMethodType::Pix => Self::BankTransfer, - api_enums::PaymentMethodType::Givex | api_enums::PaymentMethodType::PaySafeCard => { - Self::GiftCard - } + api_enums::PaymentMethodType::Givex + | api_enums::PaymentMethodType::PaySafeCard + | api_enums::PaymentMethodType::BhnCardNetwork => Self::GiftCard, api_enums::PaymentMethodType::Benefit | api_enums::PaymentMethodType::Knet | api_enums::PaymentMethodType::MomoAtm