diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index ccf8adc945..2cf101b656 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -8077,6 +8077,7 @@ "bambora", "bamboraapac", "bankofamerica", + "barclaycard", "billwerk", "bitpay", "bluesnap", @@ -21716,6 +21717,7 @@ "archipel", "authorizedotnet", "bankofamerica", + "barclaycard", "billwerk", "bitpay", "bambora", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 203b462881..15a5dbb972 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9940,6 +9940,7 @@ "bambora", "bamboraapac", "bankofamerica", + "barclaycard", "billwerk", "bitpay", "bluesnap", @@ -25969,6 +25970,7 @@ "archipel", "authorizedotnet", "bankofamerica", + "barclaycard", "billwerk", "bitpay", "bambora", diff --git a/config/config.example.toml b/config/config.example.toml index e418ab44a3..0c2fec16b2 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -188,7 +188,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 1eaa85131c..a29f54990e 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -32,7 +32,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index bef4e00e9e..16b0a3ad1d 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -36,7 +36,7 @@ authorizedotnet.base_url = "https://api.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://www.bambora.co.nz/interface/api/dts.asmx" bankofamerica.base_url = "https://api.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://bitpay.com" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index b24cd63b6f..caf6e76484 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -36,7 +36,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com" diff --git a/config/development.toml b/config/development.toml index dba50fc889..19e33b45f8 100644 --- a/config/development.toml +++ b/config/development.toml @@ -220,7 +220,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 5878c397b4..1ddef849d1 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -119,7 +119,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 1bc1403511..d86192cf0f 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -65,7 +65,7 @@ pub enum RoutableConnectors { Archipel, Authorizedotnet, Bankofamerica, - // Barclaycard, + Barclaycard, Billwerk, Bitpay, Bambora, @@ -220,7 +220,7 @@ pub enum Connector { Bambora, Bamboraapac, Bankofamerica, - // Barclaycard, + Barclaycard, Billwerk, Bitpay, Bluesnap, @@ -395,7 +395,7 @@ impl Connector { | Self::Bambora | Self::Bamboraapac | Self::Bankofamerica - // | Self::Barclaycard + | Self::Barclaycard | Self::Billwerk | Self::Bitpay | Self::Bluesnap @@ -549,7 +549,7 @@ impl From for Connector { RoutableConnectors::Archipel => Self::Archipel, RoutableConnectors::Authorizedotnet => Self::Authorizedotnet, RoutableConnectors::Bankofamerica => Self::Bankofamerica, - // RoutableConnectors::Barclaycard => Self::Barclaycard, + RoutableConnectors::Barclaycard => Self::Barclaycard, RoutableConnectors::Billwerk => Self::Billwerk, RoutableConnectors::Bitpay => Self::Bitpay, RoutableConnectors::Bambora => Self::Bambora, @@ -664,7 +664,7 @@ impl TryFrom for RoutableConnectors { Connector::Archipel => Ok(Self::Archipel), Connector::Authorizedotnet => Ok(Self::Authorizedotnet), Connector::Bankofamerica => Ok(Self::Bankofamerica), - // Connector::Barclaycard => Ok(Self::Barclaycard), + Connector::Barclaycard => Ok(Self::Barclaycard), Connector::Billwerk => Ok(Self::Billwerk), Connector::Bitpay => Ok(Self::Bitpay), Connector::Bambora => Ok(Self::Bambora), diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 4b42aa78a1..5c04e30d93 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -359,6 +359,7 @@ impl ConnectorConfig { Connector::Authorizedotnet => Ok(connector_data.authorizedotnet), Connector::Bamboraapac => Ok(connector_data.bamboraapac), Connector::Bankofamerica => Ok(connector_data.bankofamerica), + Connector::Barclaycard => Ok(connector_data.barclaycard), Connector::Billwerk => Ok(connector_data.billwerk), Connector::Bitpay => Ok(connector_data.bitpay), Connector::Bluesnap => Ok(connector_data.bluesnap), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index a393c9f6b9..ace6a7f668 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -949,6 +949,51 @@ required=true type="MultiSelect" options=["visa","masterCard","amex","discover"] +[barclaycard] +[[barclaycard.credit]] + payment_method_type = "Mastercard" +[[barclaycard.credit]] + payment_method_type = "Visa" +[[barclaycard.credit]] + payment_method_type = "AmericanExpress" +[[barclaycard.credit]] + payment_method_type = "JCB" +[[barclaycard.credit]] + payment_method_type = "Discover" +[[barclaycard.credit]] + payment_method_type = "Maestro" +[[barclaycard.credit]] + payment_method_type = "Interac" +[[barclaycard.credit]] + payment_method_type = "DinersClub" +[[barclaycard.credit]] + payment_method_type = "CartesBancaires" +[[barclaycard.credit]] + payment_method_type = "UnionPay" +[[barclaycard.debit]] + payment_method_type = "Mastercard" +[[barclaycard.debit]] + payment_method_type = "Visa" +[[barclaycard.debit]] + payment_method_type = "AmericanExpress" +[[barclaycard.debit]] + payment_method_type = "JCB" +[[barclaycard.debit]] + payment_method_type = "Discover" +[[barclaycard.debit]] + payment_method_type = "Maestro" +[[barclaycard.debit]] + payment_method_type = "Interac" +[[barclaycard.debit]] + payment_method_type = "DinersClub" +[[barclaycard.debit]] + payment_method_type = "CartesBancaires" +[[barclaycard.debit]] + payment_method_type = "UnionPay" +[barclaycard.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" [bitpay] [[bitpay.crypto]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 82070794c7..877dd08f6c 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -947,6 +947,51 @@ required=true type="MultiSelect" options=["PAN_ONLY", "CRYPTOGRAM_3DS"] +[barclaycard] +[[barclaycard.credit]] + payment_method_type = "Mastercard" +[[barclaycard.credit]] + payment_method_type = "Visa" +[[barclaycard.credit]] + payment_method_type = "AmericanExpress" +[[barclaycard.credit]] + payment_method_type = "JCB" +[[barclaycard.credit]] + payment_method_type = "Discover" +[[barclaycard.credit]] + payment_method_type = "Maestro" +[[barclaycard.credit]] + payment_method_type = "Interac" +[[barclaycard.credit]] + payment_method_type = "DinersClub" +[[barclaycard.credit]] + payment_method_type = "CartesBancaires" +[[barclaycard.credit]] + payment_method_type = "UnionPay" +[[barclaycard.debit]] + payment_method_type = "Mastercard" +[[barclaycard.debit]] + payment_method_type = "Visa" +[[barclaycard.debit]] + payment_method_type = "AmericanExpress" +[[barclaycard.debit]] + payment_method_type = "JCB" +[[barclaycard.debit]] + payment_method_type = "Discover" +[[barclaycard.debit]] + payment_method_type = "Maestro" +[[barclaycard.debit]] + payment_method_type = "Interac" +[[barclaycard.debit]] + payment_method_type = "DinersClub" +[[barclaycard.debit]] + payment_method_type = "CartesBancaires" +[[barclaycard.debit]] + payment_method_type = "UnionPay" +[barclaycard.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" [cashtocode] [[cashtocode.reward]] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 5883386d21..fcd458b378 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -949,6 +949,51 @@ required=true type="MultiSelect" options=["visa","masterCard","amex","discover"] +[barclaycard] +[[barclaycard.credit]] + payment_method_type = "Mastercard" +[[barclaycard.credit]] + payment_method_type = "Visa" +[[barclaycard.credit]] + payment_method_type = "AmericanExpress" +[[barclaycard.credit]] + payment_method_type = "JCB" +[[barclaycard.credit]] + payment_method_type = "Discover" +[[barclaycard.credit]] + payment_method_type = "Maestro" +[[barclaycard.credit]] + payment_method_type = "Interac" +[[barclaycard.credit]] + payment_method_type = "DinersClub" +[[barclaycard.credit]] + payment_method_type = "CartesBancaires" +[[barclaycard.credit]] + payment_method_type = "UnionPay" +[[barclaycard.debit]] + payment_method_type = "Mastercard" +[[barclaycard.debit]] + payment_method_type = "Visa" +[[barclaycard.debit]] + payment_method_type = "AmericanExpress" +[[barclaycard.debit]] + payment_method_type = "JCB" +[[barclaycard.debit]] + payment_method_type = "Discover" +[[barclaycard.debit]] + payment_method_type = "Maestro" +[[barclaycard.debit]] + payment_method_type = "Interac" +[[barclaycard.debit]] + payment_method_type = "DinersClub" +[[barclaycard.debit]] + payment_method_type = "CartesBancaires" +[[barclaycard.debit]] + payment_method_type = "UnionPay" +[barclaycard.connector_auth.SignatureKey] +api_key="Key" +key1="Merchant ID" +api_secret="Shared Secret" [bitpay] [[bitpay.crypto]] diff --git a/crates/hyperswitch_connectors/src/connectors/barclaycard.rs b/crates/hyperswitch_connectors/src/connectors/barclaycard.rs index 9397d063ff..950b985fa6 100644 --- a/crates/hyperswitch_connectors/src/connectors/barclaycard.rs +++ b/crates/hyperswitch_connectors/src/connectors/barclaycard.rs @@ -1,10 +1,13 @@ pub mod transformers; +use std::sync::LazyLock; +use base64::Engine; +use common_enums::enums; use common_utils::{ + consts, errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -19,10 +22,13 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -33,24 +39,72 @@ use hyperswitch_interfaces::{ configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, PaymentsVoidType, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, Maskable, PeekInterface}; +use ring::{digest, hmac}; +use time::OffsetDateTime; use transformers as barclaycard; +use url::Url; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::{self, headers}, + types::ResponseRouterData, + utils::RefundsRequestData, +}; + +pub const V_C_MERCHANT_ID: &str = "v-c-merchant-id"; #[derive(Clone)] -pub struct Barclaycard { - amount_converter: &'static (dyn AmountConvertor + Sync), -} +pub struct Barclaycard; impl Barclaycard { - pub fn new() -> &'static Self { - &Self { - amount_converter: &StringMinorUnitForConnector, - } + pub fn generate_digest(&self, payload: &[u8]) -> String { + let payload_digest = digest::digest(&digest::SHA256, payload); + consts::BASE64_ENGINE.encode(payload_digest) + } + + pub fn generate_signature( + &self, + auth: barclaycard::BarclaycardAuthType, + host: String, + resource: &str, + payload: &String, + date: OffsetDateTime, + http_method: Method, + ) -> CustomResult { + let barclaycard::BarclaycardAuthType { + api_key, + merchant_account, + api_secret, + } = auth; + let is_post_method = matches!(http_method, Method::Post); + let digest_str = if is_post_method { "digest " } else { "" }; + let headers = format!("host date (request-target) {digest_str}{V_C_MERCHANT_ID}"); + let request_target = if is_post_method { + format!("(request-target): post {resource}\ndigest: SHA-256={payload}\n") + } else { + format!("(request-target): get {resource}\n") + }; + let signature_string = format!( + "host: {host}\ndate: {date}\n{request_target}{V_C_MERCHANT_ID}: {}", + merchant_account.peek() + ); + let key_value = consts::BASE64_ENGINE + .decode(api_secret.expose()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "connector_account_details.api_secret", + })?; + let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); + let signature_value = + consts::BASE64_ENGINE.encode(hmac::sign(&key, signature_string.as_bytes()).as_ref()); + let signature_header = format!( + r#"keyid="{}", algorithm="HmacSHA256", headers="{headers}", signature="{signature_value}""#, + api_key.peek() + ); + + Ok(signature_header) } } @@ -80,15 +134,55 @@ where fn build_headers( &self, req: &RouterData, - _connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); - Ok(header) + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let date = OffsetDateTime::now_utc(); + let barclaycard_req = self.get_request_body(req, connectors)?; + let http_method = self.get_http_method(); + let auth = barclaycard::BarclaycardAuthType::try_from(&req.connector_auth_type)?; + let merchant_account = auth.merchant_account.clone(); + let base_url = connectors.barclaycard.base_url.as_str(); + let barclaycard_host = + Url::parse(base_url).change_context(errors::ConnectorError::RequestEncodingFailed)?; + let host = barclaycard_host + .host_str() + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let path: String = self + .get_url(req, connectors)? + .chars() + .skip(base_url.len() - 1) + .collect(); + let sha256 = self.generate_digest(barclaycard_req.get_inner_value().expose().as_bytes()); + let signature = self.generate_signature( + auth, + host.to_string(), + path.as_str(), + &sha256, + date, + http_method, + )?; + + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::ACCEPT.to_string(), + "application/hal+json;charset=utf-8".to_string().into(), + ), + (V_C_MERCHANT_ID.to_string(), merchant_account.into_masked()), + ("Date".to_string(), date.to_string().into()), + ("Host".to_string(), host.to_string().into()), + ("Signature".to_string(), signature.into_masked()), + ]; + if matches!(http_method, Method::Post | Method::Put) { + headers.push(( + "Digest".to_string(), + format!("SHA-256={sha256}").into_masked(), + )); + } + Ok(headers) } } @@ -112,7 +206,7 @@ impl ConnectorCommon for Barclaycard { fn get_auth_header( &self, auth_type: &ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let auth = barclaycard::BarclaycardAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -134,17 +228,83 @@ impl ConnectorCommon for Barclaycard { 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.code, - message: response.message, - reason: response.reason, - attempt_status: None, - connector_transaction_id: None, - network_decline_code: None, - network_advice_code: None, - network_error_message: None, - }) + let error_message = if res.status_code == 401 { + constants::CONNECTOR_UNAUTHORIZED_ERROR + } else { + hyperswitch_interfaces::consts::NO_ERROR_MESSAGE + }; + match response { + transformers::BarclaycardErrorResponse::StandardError(response) => { + let (code, message, reason) = match response.error_information { + Some(ref error_info) => { + let detailed_error_info = error_info.details.as_ref().map(|details| { + details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", ") + }); + ( + error_info.reason.clone(), + error_info.reason.clone(), + transformers::get_error_reason( + Some(error_info.message.clone()), + detailed_error_info, + None, + ), + ) + } + None => { + let detailed_error_info = response.details.map(|details| { + details + .iter() + .map(|det| format!("{} : {}", det.field, det.reason)) + .collect::>() + .join(", ") + }); + ( + response.reason.clone().map_or( + hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), + |reason| reason.to_string(), + ), + response + .reason + .map_or(error_message.to_string(), |reason| reason.to_string()), + transformers::get_error_reason( + response.message, + detailed_error_info, + None, + ), + ) + } + }; + + Ok(ErrorResponse { + status_code: res.status_code, + code, + message, + reason, + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } + transformers::BarclaycardErrorResponse::AuthenticationError(response) => { + Ok(ErrorResponse { + status_code: res.status_code, + code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(), + message: response.response.rmsg.clone(), + reason: Some(response.response.rmsg), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } + } } } @@ -168,7 +328,7 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -179,9 +339,12 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}pts/v2/payments", + ConnectorCommon::base_url(self, connectors) + )) } fn get_request_body( @@ -189,13 +352,12 @@ impl ConnectorIntegration CustomResult { - let amount = utils::convert_amount( - self.amount_converter, - req.request.minor_amount, + let connector_router_data = barclaycard::BarclaycardRouterData::try_from(( + &self.get_currency_unit(), req.request.currency, - )?; - - let connector_router_data = barclaycard::BarclaycardRouterData::from((amount, req)); + req.request.amount, + req, + ))?; let connector_req = barclaycard::BarclaycardPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -249,6 +411,43 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res, event_builder) } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: barclaycard::BarclaycardServerErrorResponse = res + .response + .parse_struct("BarclaycardServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + let attempt_status = match response.reason { + Some(reason) => match reason { + transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure), + transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None, + }, + None => None, + }; + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + attempt_status, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } } impl ConnectorIntegration for Barclaycard { @@ -256,7 +455,7 @@ impl ConnectorIntegration for Bar &self, req: &PaymentsSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -264,12 +463,24 @@ impl ConnectorIntegration for Bar self.common_get_content_type() } + fn get_http_method(&self) -> Method { + Method::Get + } + fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}tss/v2/transactions/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -293,9 +504,9 @@ impl ConnectorIntegration for Bar event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: barclaycard::BarclaycardPaymentsResponse = res + let response: barclaycard::BarclaycardTransactionResponse = res .response - .parse_struct("barclaycard PaymentsSyncResponse") + .parse_struct("Barclaycard PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -320,7 +531,7 @@ impl ConnectorIntegration fo &self, req: &PaymentsCaptureRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -330,18 +541,30 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{connector_payment_id}/captures", + self.base_url(connectors) + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let connector_router_data = barclaycard::BarclaycardRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount_to_capture, + req, + ))?; + let connector_req = + barclaycard::BarclaycardCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -390,16 +613,167 @@ impl ConnectorIntegration fo ) -> CustomResult { self.build_error_response(res, event_builder) } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: barclaycard::BarclaycardServerErrorResponse = res + .response + .parse_struct("BarclaycardServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } } -impl ConnectorIntegration for Barclaycard {} +impl ConnectorIntegration for Barclaycard { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{connector_payment_id}/reversals", + self.base_url(connectors) + )) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_router_data = barclaycard::BarclaycardRouterData::try_from(( + &self.get_currency_unit(), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + })?, + req.request + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Amount", + })?, + req, + ))?; + let connector_req = barclaycard::BarclaycardVoidRequest::try_from(&connector_router_data)?; + + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: barclaycard::BarclaycardPaymentsResponse = res + .response + .parse_struct("Barclaycard PaymentResponse") + .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) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: barclaycard::BarclaycardServerErrorResponse = res + .response + .parse_struct("BarclaycardServerErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|event| event.set_response_body(&response)); + router_env::logger::info!(error_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + reason: response.status.clone(), + code: response + .status + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: response + .message + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }) + } +} impl ConnectorIntegration for Barclaycard { fn get_headers( &self, req: &RefundsRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -409,10 +783,14 @@ impl ConnectorIntegration for Barclay fn get_url( &self, - _req: &RefundsRouterData, - _connectors: &Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{connector_payment_id}/refunds", + self.base_url(connectors) + )) } fn get_request_body( @@ -420,13 +798,12 @@ impl ConnectorIntegration for Barclay req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - let refund_amount = utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, + let connector_router_data = barclaycard::BarclaycardRouterData::try_from(( + &self.get_currency_unit(), req.request.currency, - )?; - - let connector_router_data = barclaycard::BarclaycardRouterData::from((refund_amount, req)); + req.request.refund_amount, + req, + ))?; let connector_req = barclaycard::BarclaycardRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -457,7 +834,7 @@ impl ConnectorIntegration for Barclay event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: barclaycard::RefundResponse = res + let response: barclaycard::BarclaycardRefundResponse = res .response .parse_struct("barclaycard RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -484,7 +861,7 @@ impl ConnectorIntegration for Barclayca &self, req: &RefundSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -492,12 +869,20 @@ impl ConnectorIntegration for Barclayca self.common_get_content_type() } + fn get_http_method(&self) -> Method { + Method::Get + } + fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req.request.get_connector_refund_id()?; + Ok(format!( + "{}tss/v2/transactions/{refund_id}", + self.base_url(connectors) + )) } fn build_request( @@ -524,7 +909,7 @@ impl ConnectorIntegration for Barclayca event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: barclaycard::RefundResponse = res + let response: barclaycard::BarclaycardRsyncResponse = res .response .parse_struct("barclaycard RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -570,4 +955,88 @@ impl webhooks::IncomingWebhook for Barclaycard { } } -impl ConnectorSpecifications for Barclaycard {} +static BARCLAYCARD_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + let supported_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::Manual, + enums::CaptureMethod::SequentialAutomatic, + ]; + + let supported_card_network = vec![ + common_enums::CardNetwork::Mastercard, + common_enums::CardNetwork::Visa, + common_enums::CardNetwork::AmericanExpress, + common_enums::CardNetwork::JCB, + common_enums::CardNetwork::Discover, + common_enums::CardNetwork::Maestro, + common_enums::CardNetwork::Interac, + common_enums::CardNetwork::DinersClub, + common_enums::CardNetwork::CartesBancaires, + common_enums::CardNetwork::UnionPay, + ]; + + let mut barclaycard_supported_payment_methods = SupportedPaymentMethods::new(); + + barclaycard_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + }, + ); + + barclaycard_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods, + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network, + } + }), + ), + }, + ); + + barclaycard_supported_payment_methods + }); + +static BARCLAYCARD_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "BarclayCard SmartPay Fuse", + description: "Barclaycard, part of Barclays Bank UK PLC, is a leading global payment business that helps consumers, retailers and businesses to make and take payments flexibly, and to access short-term credit and point of sale finance.", + connector_type: enums::PaymentConnectorCategory::BankAcquirer, +}; + +static BARCLAYCARD_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for Barclaycard { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&BARCLAYCARD_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*BARCLAYCARD_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&BARCLAYCARD_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs b/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs index 1a994d3d2a..14425bb118 100644 --- a/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs @@ -1,52 +1,458 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::pii; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{ + AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData, + ErrorResponse, RouterData, + }, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, + ResponseId, + }, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, }; -use hyperswitch_interfaces::errors; -use masking::Secret; +use hyperswitch_interfaces::{api, errors}; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use crate::{ + constants, types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{ + self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData, PaymentsSyncRequestData, + RouterData as OtherRouterData, + }, }; - -//TODO: Fill the struct with respective fields -pub struct BarclaycardRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. - pub router_data: T, +pub struct BarclaycardAuthType { + pub(super) api_key: Secret, + pub(super) merchant_account: Secret, + pub(super) api_secret: Secret, } -impl From<(StringMinorUnit, T)> for BarclaycardRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts - Self { - amount, - router_data: item, +impl TryFrom<&ConnectorAuthType> for BarclaycardAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } = auth_type + { + Ok(Self { + api_key: api_key.to_owned(), + merchant_account: key1.to_owned(), + api_secret: api_secret.to_owned(), + }) + } else { + Err(errors::ConnectorError::FailedToObtainAuthType)? } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct BarclaycardPaymentsRequest { - amount: StringMinorUnit, - card: BarclaycardCard, +pub struct BarclaycardRouterData { + pub amount: String, + pub router_data: T, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct BarclaycardCard { +impl TryFrom<(&api::CurrencyUnit, api_models::enums::Currency, i64, T)> + for BarclaycardRouterData +{ + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): ( + &api::CurrencyUnit, + api_models::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardPaymentsRequest { + processing_information: ProcessingInformation, + payment_information: PaymentInformation, + order_information: OrderInformationWithBill, + client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + consumer_authentication_information: Option, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessingInformation { + commerce_indicator: String, + capture: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantDefinedInformation { + key: u8, + value: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardConsumerAuthInformation { + ucaf_collection_indicator: Option, + cavv: Option, + ucaf_authentication_data: Option>, + xid: Option, + directory_server_transaction_id: Option>, + specification_version: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardPaymentInformation { + card: Card, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum PaymentInformation { + Cards(Box), +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Card { number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + expiration_month: Secret, + expiration_year: Secret, + security_code: Secret, + #[serde(rename = "type")] + card_type: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformationWithBill { + amount_details: Amount, + bill_to: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + total_amount: String, + currency: api_models::enums::Currency, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BillTo { + first_name: Option>, + last_name: Option>, + address1: Option>, + locality: Option, + #[serde(skip_serializing_if = "Option::is_none")] + administrative_area: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + postal_code: Option>, + country: Option, + email: pii::Email, +} + +fn build_bill_to( + address_details: Option<&hyperswitch_domain_models::address::Address>, + email: pii::Email, +) -> Result> { + let default_address = BillTo { + first_name: None, + last_name: None, + address1: None, + locality: None, + administrative_area: None, + postal_code: None, + country: None, + email: email.clone(), + }; + + Ok(address_details + .and_then(|addr| { + addr.address.as_ref().map(|addr| { + let administrative_area = addr.to_state_code_as_optional().unwrap_or_else(|_| { + addr.state + .clone() + .map(|state| Secret::new(format!("{:.20}", state.expose()))) + }); + + BillTo { + first_name: addr.first_name.clone(), + last_name: addr.last_name.clone(), + address1: addr.line1.clone(), + locality: addr.city.clone(), + administrative_area, + postal_code: addr.zip.clone(), + country: addr.country, + email, + } + }) + }) + .unwrap_or(default_address)) +} + +fn get_barclaycard_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { + match card_network { + common_enums::CardNetwork::Visa => Some("001"), + common_enums::CardNetwork::Mastercard => Some("002"), + common_enums::CardNetwork::AmericanExpress => Some("003"), + common_enums::CardNetwork::JCB => Some("007"), + common_enums::CardNetwork::DinersClub => Some("005"), + common_enums::CardNetwork::Discover => Some("004"), + common_enums::CardNetwork::CartesBancaires => Some("006"), + common_enums::CardNetwork::UnionPay => Some("062"), + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + common_enums::CardNetwork::Maestro => Some("042"), + common_enums::CardNetwork::Interac + | common_enums::CardNetwork::RuPay + | common_enums::CardNetwork::Star + | common_enums::CardNetwork::Accel + | common_enums::CardNetwork::Pulse + | common_enums::CardNetwork::Nyce => None, + } +} + +impl + From<( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + Option, + )> for OrderInformationWithBill +{ + fn from( + (item, bill_to): ( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + Option, + ), + ) -> Self { + Self { + amount_details: Amount { + total_amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + }, + bill_to, + } + } +} + +impl + TryFrom<( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + Option, + )> for ProcessingInformation +{ + type Error = error_stack::Report; + + fn try_from( + (item, network): ( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + Option, + ), + ) -> Result { + let commerce_indicator = get_commerce_indicator(network); + + Ok(Self { + capture: Some(matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) | None + )), + commerce_indicator, + }) + } +} + +impl From<&BarclaycardRouterData<&PaymentsAuthorizeRouterData>> for ClientReferenceInformation { + fn from(item: &BarclaycardRouterData<&PaymentsAuthorizeRouterData>) -> Self { + Self { + code: Some(item.router_data.connector_request_reference_id.clone()), + } + } +} + +fn convert_metadata_to_merchant_defined_info(metadata: Value) -> Vec { + let hashmap: std::collections::BTreeMap = + serde_json::from_str(&metadata.to_string()).unwrap_or(std::collections::BTreeMap::new()); + let mut vector = Vec::new(); + let mut iter = 1; + for (key, value) in hashmap { + vector.push(MerchantDefinedInformation { + key: iter, + value: format!("{key}={value}"), + }); + iter += 1; + } + vector +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientReferenceInformation { + code: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientProcessorInformation { + avs: Option, + card_verification: Option, + processor: Option, + network_transaction_id: Option>, + approval_code: Option, + merchant_advice: Option, + response_code: Option, + ach_verification: Option, + system_trace_audit_number: Option, + event_status: Option, + retrieval_reference_number: Option, + consumer_authentication_response: Option, + response_details: Option, + transaction_id: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantAdvice { + code: Option, + code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsumerAuthenticationResponse { + code: Option, + code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AchVerification { + result_code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessorResponse { + name: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardVerification { + result_code: Option, + result_code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientRiskInformation { + rules: Option>, + profile: Option, + score: Option, + info_codes: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InfoCodes { + address: Option>, + identity_change: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Score { + factor_codes: Option>, + result: Option, + model_used: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RiskResult { + StringVariant(String), + IntVariant(u64), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Profile { + early_decision: Option, + name: Option, + decision: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ClientRiskInformationRules { + name: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Avs { + code: Option, + code_raw: Option, +} + +impl + TryFrom<( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, + )> for BarclaycardPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::Card, + ), + ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "Barclaycard", + })? + }; + + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + let payment_information = PaymentInformation::try_from(&ccard)?; + let processing_information = ProcessingInformation::try_from((item, None))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(convert_metadata_to_merchant_defined_info); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + merchant_defined_information, + consumer_authentication_information: None, + }) + } } impl TryFrom<&BarclaycardRouterData<&PaymentsAuthorizeRouterData>> for BarclaycardPaymentsRequest { @@ -55,174 +461,1157 @@ impl TryFrom<&BarclaycardRouterData<&PaymentsAuthorizeRouterData>> for Barclayca item: &BarclaycardRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = BarclaycardCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) + PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), + PaymentMethodData::Wallet(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Barclaycard"), + ) + .into()) } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct -pub struct BarclaycardAuthType { - pub(super) api_key: Secret, -} - -impl TryFrom<&ConnectorAuthType> for BarclaycardAuthType { - 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(), - }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), - } - } -} -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BarclaycardPaymentStatus { + Authorized, Succeeded, Failed, - #[default] - Processing, + Voided, + Reversed, + Pending, + Declined, + Rejected, + Challenge, + AuthorizedPendingReview, + AuthorizedRiskDeclined, + Transmitted, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + Accepted, + Cancelled, + //PartialAuthorized, not being consumed yet. } -impl From for common_enums::AttemptStatus { - fn from(item: BarclaycardPaymentStatus) -> Self { - match item { - BarclaycardPaymentStatus::Succeeded => Self::Charged, - BarclaycardPaymentStatus::Failed => Self::Failure, - BarclaycardPaymentStatus::Processing => Self::Authorizing, +fn map_barclaycard_attempt_status( + (status, auto_capture): (BarclaycardPaymentStatus, bool), +) -> enums::AttemptStatus { + match status { + BarclaycardPaymentStatus::Authorized + | BarclaycardPaymentStatus::AuthorizedPendingReview => { + if auto_capture { + // Because Barclaycard will return Payment Status as Authorized even in AutoCapture Payment + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized + } } + BarclaycardPaymentStatus::Pending => { + if auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Pending + } + } + BarclaycardPaymentStatus::Succeeded | BarclaycardPaymentStatus::Transmitted => { + enums::AttemptStatus::Charged + } + BarclaycardPaymentStatus::Voided + | BarclaycardPaymentStatus::Reversed + | BarclaycardPaymentStatus::Cancelled => enums::AttemptStatus::Voided, + BarclaycardPaymentStatus::Failed + | BarclaycardPaymentStatus::Declined + | BarclaycardPaymentStatus::AuthorizedRiskDeclined + | BarclaycardPaymentStatus::InvalidRequest + | BarclaycardPaymentStatus::Rejected + | BarclaycardPaymentStatus::ServerError => enums::AttemptStatus::Failure, + BarclaycardPaymentStatus::PendingAuthentication => { + enums::AttemptStatus::AuthenticationPending + } + BarclaycardPaymentStatus::PendingReview + | BarclaycardPaymentStatus::Challenge + | BarclaycardPaymentStatus::Accepted => enums::AttemptStatus::Pending, } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BarclaycardPaymentsResponse { - status: BarclaycardPaymentStatus, - id: String, +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BarclaycardPaymentsResponse { + ClientReferenceInformation(Box), + ErrorInformation(Box), } -impl TryFrom> - for RouterData +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardClientReferenceResponse { + id: String, + status: BarclaycardPaymentStatus, + client_reference_information: ClientReferenceInformation, + processor_information: Option, + processing_information: Option, + payment_information: Option, + payment_insights_information: Option, + risk_information: Option, + error_information: Option, + issuer_information: Option, + sender_information: Option, + payment_account_information: Option, + reconciliation_id: Option, + consumer_authentication_information: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsumerAuthenticationInformation { + eci_raw: Option, + eci: Option, + acs_transaction_id: Option, + cavv: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SenderInformation { + payment_information: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentInsightsInformation { + response_insights: Option, + rule_results: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResponseInsights { + category_code: Option, + category: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RuleResults { + id: Option, + decision: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentInformationResponse { + tokenized_card: Option, + customer: Option, + card: Option, + scheme: Option, + bin: Option, + account_type: Option, + issuer: Option, + bin_country: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CustomerResponseObject { + customer_id: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountInformation { + card: Option, + features: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountFeatureInformation { + health_card: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountCardInformation { + #[serde(rename = "type")] + card_type: Option, + hashed_number: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessingInformationResponse { + payment_solution: Option, + commerce_indicator: Option, + commerce_indicator_label: Option, + ecommerce_indicator: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IssuerInformation { + country: Option, + discretionary_data: Option, + country_specific_discretionary_data: Option, + response_code: Option, + pin_request_indicator: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardResponseObject { + suffix: Option, + prefix: Option, + expiration_month: Option>, + expiration_year: Option>, + #[serde(rename = "type")] + card_type: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardErrorInformationResponse { + id: String, + error_information: BarclaycardErrorInformation, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct BarclaycardErrorInformation { + reason: Option, + message: Option, + details: Option>, +} + +fn map_error_response( + error_response: &BarclaycardErrorInformationResponse, + item: ResponseRouterData, + transaction_status: Option, +) -> RouterData { + let detailed_error_info = error_response + .error_information + .details + .as_ref() + .map(|details| { + details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") + }); + + let reason = get_error_reason( + error_response.error_information.message.clone(), + detailed_error_info, + None, + ); + let response = Err(ErrorResponse { + code: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_response + .error_information + .reason + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(error_response.id.clone()), + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }); + + match transaction_status { + Some(status) => RouterData { + response, + status, + ..item.data + }, + None => RouterData { + response, + ..item.data + }, + } +} + +fn get_error_response_if_failure( + (info_response, status, http_code): ( + &BarclaycardClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Option { + if utils::is_payment_failure(status) { + Some(get_error_response( + &info_response.error_information, + &info_response.processor_information, + &info_response.risk_information, + Some(status), + http_code, + info_response.id.clone(), + )) + } else { + None + } +} + +fn get_payment_response( + (info_response, status, http_code): ( + &BarclaycardClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Result> { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(Box::new(error)), + None => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed: None, + charges: None, + }), + } +} + +impl + TryFrom< + ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, ) -> 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, + match item.response { + BarclaycardPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = map_barclaycard_attempt_status(( + info_response.status.clone(), + item.data.request.is_auto_capture()?, + )); + let response = get_payment_response((&info_response, status, item.http_code)) + .map_err(|err| *err); + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => info_response + .processor_information + .as_ref() + .and_then(|processor_information| { + info_response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + convert_to_additional_payment_method_connector_response( + processor_information, + consumer_auth_information, + ) + }) + }) + .map(ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::RealTimePayment + | common_enums::PaymentMethod::MobilePayment + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::OpenBanking + | common_enums::PaymentMethod::GiftCard => None, + }; + + Ok(Self { + status, + response, + connector_response, + ..item.data + }) + } + BarclaycardPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(map_error_response( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + )) + } + } + } +} + +fn convert_to_additional_payment_method_connector_response( + processor_information: &ClientProcessorInformation, + consumer_authentication_information: &ConsumerAuthenticationInformation, +) -> AdditionalPaymentMethodConnectorResponse { + let payment_checks = Some(serde_json::json!({ + "avs_response": processor_information.avs, + "card_verification": processor_information.card_verification, + "approval_code": processor_information.approval_code, + "consumer_authentication_response": processor_information.consumer_authentication_response, + "cavv": consumer_authentication_information.cavv, + "eci": consumer_authentication_information.eci, + "eci_raw": consumer_authentication_information.eci_raw, + })); + + let authentication_data = Some(serde_json::json!({ + "retrieval_reference_number": processor_information.retrieval_reference_number, + "acs_transaction_id": consumer_authentication_information.acs_transaction_id, + "system_trace_audit_number": processor_information.system_trace_audit_number, + })); + + AdditionalPaymentMethodConnectorResponse::Card { + authentication_data, + payment_checks, + card_network: None, + domestic_network: None, + } +} + +impl + TryFrom< + ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, + ) -> Result { + match item.response { + BarclaycardPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = map_barclaycard_attempt_status((info_response.status.clone(), true)); + let response = get_payment_response((&info_response, status, item.http_code)) + .map_err(|err| *err); + Ok(Self { + status, + response, + ..item.data + }) + } + BarclaycardPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(map_error_response(&error_response.clone(), item, None)) + } + } + } +} + +impl + TryFrom< + ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsCancelData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + BarclaycardPaymentsResponse, + PaymentsCancelData, + PaymentsResponseData, + >, + ) -> Result { + match item.response { + BarclaycardPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = map_barclaycard_attempt_status((info_response.status.clone(), false)); + let response = get_payment_response((&info_response, status, item.http_code)) + .map_err(|err| *err); + Ok(Self { + status, + response, + ..item.data + }) + } + BarclaycardPaymentsResponse::ErrorInformation(ref error_response) => { + Ok(map_error_response(&error_response.clone(), item, None)) + } + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardTransactionResponse { + id: String, + application_information: ApplicationInformation, + client_reference_information: Option, + processor_information: Option, + processing_information: Option, + payment_information: Option, + payment_insights_information: Option, + error_information: Option, + fraud_marking_information: Option, + risk_information: Option, + reconciliation_id: Option, + consumer_authentication_information: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FraudMarkingInformation { + reason: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationInformation { + status: Option, +} + +impl + TryFrom< + ResponseRouterData< + F, + BarclaycardTransactionResponse, + PaymentsSyncData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + BarclaycardTransactionResponse, + PaymentsSyncData, + PaymentsResponseData, + >, + ) -> Result { + match item.response.application_information.status { + Some(app_status) => { + let status = map_barclaycard_attempt_status(( + app_status, + item.data.request.is_auto_capture()?, + )); + + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => item + .response + .processor_information + .as_ref() + .and_then(|processor_information| { + item.response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + convert_to_additional_payment_method_connector_response( + processor_information, + consumer_auth_information, + ) + }) + }) + .map(ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::RealTimePayment + | common_enums::PaymentMethod::MobilePayment + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::OpenBanking + | common_enums::PaymentMethod::GiftCard => None, + }; + + let risk_info: Option = None; + if utils::is_payment_failure(status) { + Ok(Self { + response: Err(get_error_response( + &item.response.error_information, + &item.response.processor_information, + &risk_info, + Some(status), + item.http_code, + item.response.id.clone(), + )), + status: enums::AttemptStatus::Failure, + connector_response, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: item + .response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(item.response.id)), + incremental_authorization_allowed: None, + charges: None, + }), + connector_response, + ..item.data + }) + } + } + None => Ok(Self { + status: item.data.status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.id), + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data }), - ..item.data + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformation { + amount_details: Amount, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardCaptureRequest { + order_information: OrderInformation, + client_reference_information: ClientReferenceInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, +} + +impl TryFrom<&BarclaycardRouterData<&PaymentsCaptureRouterData>> for BarclaycardCaptureRequest { + type Error = error_stack::Report; + fn try_from( + value: &BarclaycardRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + let merchant_defined_information = value + .router_data + .request + .metadata + .clone() + .map(convert_metadata_to_merchant_defined_info); + Ok(Self { + order_information: OrderInformation { + amount_details: Amount { + total_amount: value.amount.to_owned(), + currency: value.router_data.request.currency, + }, + }, + client_reference_information: ClientReferenceInformation { + code: Some(value.router_data.connector_request_reference_id.clone()), + }, + merchant_defined_information, }) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardVoidRequest { + client_reference_information: ClientReferenceInformation, + reversal_information: ReversalInformation, + #[serde(skip_serializing_if = "Option::is_none")] + merchant_defined_information: Option>, + // The connector documentation does not mention the merchantDefinedInformation field for Void requests. But this has been still added because it works! +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ReversalInformation { + amount_details: Amount, + reason: String, +} + +impl TryFrom<&BarclaycardRouterData<&PaymentsCancelRouterData>> for BarclaycardVoidRequest { + type Error = error_stack::Report; + fn try_from( + value: &BarclaycardRouterData<&PaymentsCancelRouterData>, + ) -> Result { + let merchant_defined_information = value + .router_data + .request + .metadata + .clone() + .map(convert_metadata_to_merchant_defined_info); + Ok(Self { + client_reference_information: ClientReferenceInformation { + code: Some(value.router_data.connector_request_reference_id.clone()), + }, + reversal_information: ReversalInformation { + amount_details: Amount { + total_amount: value.amount.to_owned(), + currency: value.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "Currency", + }, + )?, + }, + reason: value + .router_data + .request + .cancellation_reason + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "Cancellation Reason", + })?, + }, + merchant_defined_information, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct BarclaycardRefundRequest { - pub amount: StringMinorUnit, + order_information: OrderInformation, + client_reference_information: ClientReferenceInformation, } impl TryFrom<&BarclaycardRouterData<&RefundsRouterData>> for BarclaycardRefundRequest { type Error = error_stack::Report; fn try_from(item: &BarclaycardRouterData<&RefundsRouterData>) -> Result { Ok(Self { - amount: item.amount.to_owned(), + order_information: OrderInformation { + amount_details: Amount { + total_amount: item.amount.clone(), + currency: item.router_data.request.currency, + }, + }, + client_reference_information: ClientReferenceInformation { + code: Some(item.router_data.request.refund_id.clone()), + }, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping +impl From for enums::RefundStatus { + fn from(item: BarclaycardRefundResponse) -> Self { + let error_reason = item + .error_information + .and_then(|error_info| error_info.reason); + match item.status { + BarclaycardRefundStatus::Succeeded | BarclaycardRefundStatus::Transmitted => { + Self::Success + } + BarclaycardRefundStatus::Cancelled + | BarclaycardRefundStatus::Failed + | BarclaycardRefundStatus::Voided => Self::Failure, + BarclaycardRefundStatus::Pending => Self::Pending, + BarclaycardRefundStatus::TwoZeroOne => { + if error_reason == Some("PROCESSOR_DECLINED".to_string()) { + Self::Failure + } else { + Self::Pending + } + } } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardRefundResponse { id: String, - status: RefundStatus, + status: BarclaycardRefundStatus, + error_information: Option, } -impl TryFrom> for RefundsRouterData { +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { + let refund_status = enums::RefundStatus::from(item.response.clone()); + let response = if utils::is_refund_failure(refund_status) { + Err(get_error_response( + &item.response.error_information, + &None, + &None, + None, + item.http_code, + item.response.id, + )) + } else { + Ok(RefundsResponseData { + connector_refund_id: item.response.id, + refund_status, + }) + }; + Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), + response, ..item.data }) } } -impl TryFrom> for RefundsRouterData { +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum BarclaycardRefundStatus { + Succeeded, + Transmitted, + Failed, + Pending, + Voided, + Cancelled, + #[serde(rename = "201")] + TwoZeroOne, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RsyncApplicationInformation { + status: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardRsyncResponse { + id: String, + application_information: Option, + error_information: Option, +} + +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + let response = match item + .response + .application_information + .and_then(|application_information| application_information.status) + { + Some(status) => { + let error_reason = item + .response + .error_information + .clone() + .and_then(|error_info| error_info.reason); + let refund_status = match status { + BarclaycardRefundStatus::Succeeded | BarclaycardRefundStatus::Transmitted => { + enums::RefundStatus::Success + } + BarclaycardRefundStatus::Cancelled + | BarclaycardRefundStatus::Failed + | BarclaycardRefundStatus::Voided => enums::RefundStatus::Failure, + BarclaycardRefundStatus::Pending => enums::RefundStatus::Pending, + BarclaycardRefundStatus::TwoZeroOne => { + if error_reason == Some("PROCESSOR_DECLINED".to_string()) { + enums::RefundStatus::Failure + } else { + enums::RefundStatus::Pending + } + } + }; + if utils::is_refund_failure(refund_status) { + if status == BarclaycardRefundStatus::Voided { + Err(get_error_response( + &Some(BarclaycardErrorInformation { + message: Some(constants::REFUND_VOIDED.to_string()), + reason: Some(constants::REFUND_VOIDED.to_string()), + details: None, + }), + &None, + &None, + None, + item.http_code, + item.response.id.clone(), + )) + } else { + Err(get_error_response( + &item.response.error_information, + &None, + &None, + None, + item.http_code, + item.response.id.clone(), + )) + } + } else { + Ok(RefundsResponseData { + connector_refund_id: item.response.id, + refund_status, + }) + } + } + + None => Ok(RefundsResponseData { + connector_refund_id: item.response.id.clone(), + refund_status: match item.data.response { + Ok(response) => response.refund_status, + Err(_) => common_enums::RefundStatus::Pending, + }, }), + }; + + Ok(Self { + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct BarclaycardErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardStandardErrorResponse { + pub error_information: Option, + pub status: Option, + pub message: Option, pub reason: Option, + pub details: Option>, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BarclaycardServerErrorResponse { + pub status: Option, + pub message: Option, + pub reason: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum Reason { + SystemError, + ServerTimeout, + ServiceTimeout, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BarclaycardAuthenticationErrorResponse { + pub response: AuthenticationErrorInformation, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BarclaycardErrorResponse { + AuthenticationError(BarclaycardAuthenticationErrorResponse), + StandardError(BarclaycardStandardErrorResponse), +} + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Details { + pub field: String, + pub reason: String, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct ErrorInformation { + pub message: String, + pub reason: String, + pub details: Option>, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct AuthenticationErrorInformation { + pub rmsg: String, +} + +fn get_error_response( + error_data: &Option, + processor_information: &Option, + risk_information: &Option, + attempt_status: Option, + status_code: u16, + transaction_id: String, +) -> ErrorResponse { + let avs_message = risk_information + .clone() + .map(|client_risk_information| { + client_risk_information.rules.map(|rules| { + rules + .iter() + .map(|risk_info| { + risk_info.name.clone().map_or("".to_string(), |name| { + format!(" , {}", name.clone().expose()) + }) + }) + .collect::>() + .join("") + }) + }) + .unwrap_or(Some("".to_string())); + + let detailed_error_info = error_data.to_owned().and_then(|error_info| { + error_info.details.map(|error_details| { + error_details + .iter() + .map(|details| format!("{} : {}", details.field, details.reason)) + .collect::>() + .join(", ") + }) + }); + let network_decline_code = processor_information + .as_ref() + .and_then(|info| info.response_code.clone()); + let network_advice_code = processor_information.as_ref().and_then(|info| { + info.merchant_advice + .as_ref() + .and_then(|merchant_advice| merchant_advice.code_raw.clone()) + }); + + let reason = get_error_reason( + error_data + .clone() + .and_then(|error_details| error_details.message), + detailed_error_info, + avs_message, + ); + let error_message = error_data + .clone() + .and_then(|error_details| error_details.reason); + + ErrorResponse { + code: error_message + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_message + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason, + status_code, + attempt_status, + connector_transaction_id: Some(transaction_id.clone()), + network_advice_code, + network_decline_code, + network_error_message: None, + } +} + +impl TryFrom<&hyperswitch_domain_models::payment_method_data::Card> for PaymentInformation { + type Error = error_stack::Report; + + fn try_from( + ccard: &hyperswitch_domain_models::payment_method_data::Card, + ) -> Result { + let card_type = match ccard + .card_network + .clone() + .and_then(get_barclaycard_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), + }; + Ok(Self::Cards(Box::new(CardPaymentInformation { + card: Card { + number: ccard.card_number.clone(), + expiration_month: ccard.card_exp_month.clone(), + expiration_year: ccard.card_exp_year.clone(), + security_code: ccard.card_cvc.clone(), + card_type, + }, + }))) + } +} + +fn get_commerce_indicator(network: Option) -> String { + match network { + Some(card_network) => match card_network.to_lowercase().as_str() { + "amex" => "aesk", + "discover" => "dipb", + "mastercard" => "spa", + "visa" => "internet", + _ => "internet", + }, + None => "internet", + } + .to_string() +} + +pub fn get_error_reason( + error_info: Option, + detailed_error_info: Option, + avs_error_info: Option, +) -> Option { + match (error_info, detailed_error_info, avs_error_info) { + (Some(message), Some(details), Some(avs_message)) => Some(format!( + "{}, detailed_error_information: {}, avs_message: {}", + message, details, avs_message + )), + (Some(message), Some(details), None) => Some(format!( + "{}, detailed_error_information: {}", + message, details + )), + (Some(message), None, Some(avs_message)) => { + Some(format!("{}, avs_message: {}", message, avs_message)) + } + (None, Some(details), Some(avs_message)) => { + Some(format!("{}, avs_message: {}", details, avs_message)) + } + (Some(message), None, None) => Some(message), + (None, Some(details), None) => Some(details), + (None, None, Some(avs_message)) => Some(avs_message), + (None, None, None) => None, + } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 2ab4e68715..ba078fde73 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1412,10 +1412,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { bankofamerica::transformers::BankOfAmericaAuthType::try_from(self.auth_type)?; Ok(()) } - // api_enums::Connector::Barclaycard => { - // barclaycard::transformers::BarclaycardAuthType::try_from(self.auth_type)?; - // Ok(()) - // }, + api_enums::Connector::Barclaycard => { + barclaycard::transformers::BarclaycardAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Billwerk => { billwerk::transformers::BillwerkAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 3328c62b73..f41fa33bb2 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -352,9 +352,9 @@ impl ConnectorData { enums::Connector::Bankofamerica => { Ok(ConnectorEnum::Old(Box::new(&connector::Bankofamerica))) } - // enums::Connector::Barclaycard => { - // Ok(ConnectorEnum::Old(Box::new(connector::Barclaycard))) - // } + enums::Connector::Barclaycard => { + Ok(ConnectorEnum::Old(Box::new(&connector::Barclaycard))) + } enums::Connector::Billwerk => { Ok(ConnectorEnum::Old(Box::new(connector::Billwerk::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index fadaf943be..f4421242a4 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -219,7 +219,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Bambora => Self::Bambora, api_enums::Connector::Bamboraapac => Self::Bamboraapac, api_enums::Connector::Bankofamerica => Self::Bankofamerica, - // api_enums::Connector::Barclaycard => Self::Barclaycard, + api_enums::Connector::Barclaycard => Self::Barclaycard, api_enums::Connector::Billwerk => Self::Billwerk, api_enums::Connector::Bitpay => Self::Bitpay, api_enums::Connector::Bluesnap => Self::Bluesnap, diff --git a/crates/router/tests/connectors/barclaycard.rs b/crates/router/tests/connectors/barclaycard.rs index 2087131c4e..c2c21bbcc0 100644 --- a/crates/router/tests/connectors/barclaycard.rs +++ b/crates/router/tests/connectors/barclaycard.rs @@ -11,7 +11,7 @@ impl utils::Connector for BarclaycardTest { fn get_data(&self) -> api::ConnectorData { use router::connector::Barclaycard; utils::construct_connector_data_old( - Box::new(Barclaycard::new()), + Box::new(&Barclaycard), types::Connector::DummyConnector1, api::GetToken::Connector, None, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 7eb83b4ffe..e491b839ff 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -86,7 +86,7 @@ authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" -barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/pts/v2/" +barclaycard.base_url = "https://api.smartpayfuse-test.barclaycard/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" bitpay.base_url = "https://test.bitpay.com"