diff --git a/config/config.example.toml b/config/config.example.toml index 9c57f431ee..92bdaf6b92 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -186,7 +186,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" -bamboraapac.base_url = "https://demo.bambora.co.nz/interface/api/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 7c946f8209..a498ad3899 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -26,7 +26,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" 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/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 5fb7bfe0d6..e79e096436 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -30,7 +30,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" 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/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" diff --git a/config/development.toml b/config/development.toml index 88dff87fe8..108e67e884 100644 --- a/config/development.toml +++ b/config/development.toml @@ -182,7 +182,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" 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/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 7c750dd770..f6cb52803a 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -115,7 +115,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" -bamboraapac.base_url = "https://demo.bambora.co.nz/interface/api/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/" diff --git a/crates/router/src/connector/bamboraapac.rs b/crates/router/src/connector/bamboraapac.rs index 335453125e..82ed134a97 100644 --- a/crates/router/src/connector/bamboraapac.rs +++ b/crates/router/src/connector/bamboraapac.rs @@ -92,6 +92,22 @@ impl ConnectorValidation for Bamboraapac { ), } } + + fn validate_mandate_payment( + &self, + _pm_type: Option, + pm_data: types::domain::payments::PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let connector = self.id(); + match pm_data { + types::domain::payments::PaymentMethodData::Card(_) => Ok(()), + _ => Err(errors::ConnectorError::NotSupported { + message: "mandate payment".to_string(), + connector, + } + .into()), + } + } } impl ConnectorCommon for Bamboraapac { @@ -167,6 +183,85 @@ impl types::PaymentsResponseData, > for Bamboraapac { + fn get_headers( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::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: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}/sipp.asmx", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = bamboraapac::get_setup_mandate_body(req)?; + + Ok(RequestContent::RawBytes(connector_req)) + } + + fn build_request( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .set_body(types::SetupMandateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response_data = html_to_xml_string_conversion( + String::from_utf8(res.response.to_vec()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, + ); + + let response = response_data + .parse_xml::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::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) + } } impl ConnectorIntegration @@ -189,7 +284,7 @@ impl ConnectorIntegration CustomResult { - Ok(self.base_url(connectors).to_string()) + Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( @@ -284,7 +379,7 @@ impl ConnectorIntegration CustomResult { - Ok(self.base_url(connectors).to_string()) + Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( @@ -368,7 +463,7 @@ impl ConnectorIntegration CustomResult { - Ok(self.base_url(connectors).to_string()) + Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( @@ -467,7 +562,7 @@ impl ConnectorIntegration CustomResult { - Ok(self.base_url(connectors).to_string()) + Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( @@ -560,7 +655,7 @@ impl ConnectorIntegration CustomResult { - Ok(self.base_url(connectors).to_string()) + Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( diff --git a/crates/router/src/connector/bamboraapac/transformers.rs b/crates/router/src/connector/bamboraapac/transformers.rs index d660278fba..5125fafcee 100644 --- a/crates/router/src/connector/bamboraapac/transformers.rs +++ b/crates/router/src/connector/bamboraapac/transformers.rs @@ -5,7 +5,7 @@ use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, CardData, RouterData}, + connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData}, core::errors, types::{self, domain, storage::enums, transformers::ForeignFrom}, }; @@ -94,21 +94,52 @@ fn get_card_data(req: &types::PaymentsAuthorizeRouterData) -> Result { + if req.request.setup_future_usage == Some(enums::FutureUsage::OffSession) { + format!( + r#" + + 2 + {} + {} + {} + {} + {} + + "#, + card.card_number.get_card_no(), + card.card_exp_month.peek(), + card.get_expiry_year_4_digit().peek(), + card.card_cvc.peek(), + card_holder_name.peek(), + ) + } else { + format!( + r#" + + {} + {} + {} + {} + {} + + "#, + card.card_number.get_card_no(), + card.card_exp_month.peek(), + card.get_expiry_year_4_digit().peek(), + card.card_cvc.peek(), + card_holder_name.peek(), + ) + } + } + domain::PaymentMethodData::MandatePayment => { format!( r#" - - {} - {} - {} - {} - {} + + 2 + {} "#, - card.card_number.get_card_no(), - card.card_exp_month.peek(), - card.get_expiry_year_4_digit().peek(), - card.card_cvc.peek(), - card_holder_name.peek(), + req.request.get_connector_mandate_id()? ) } _ => { @@ -182,6 +213,7 @@ pub struct SubmitSinglePaymentResult { pub struct PaymentResponse { response_code: u8, receipt: String, + credit_card_token: Option, declined_code: Option, declined_message: Option, } @@ -234,6 +266,23 @@ impl .submit_single_payment_result .response .receipt; + + let mandate_reference = + if item.data.request.setup_future_usage == Some(enums::FutureUsage::OffSession) { + let connector_mandate_id = item + .response + .body + .submit_single_payment_response + .submit_single_payment_result + .response + .credit_card_token; + Some(types::MandateReference { + connector_mandate_id, + payment_method_id: None, + }) + } else { + None + }; // transaction approved if response_code == 0 { Ok(Self { @@ -243,7 +292,7 @@ impl connector_transaction_id.to_owned(), ), redirection_data: None, - mandate_reference: None, + mandate_reference, connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), @@ -288,6 +337,159 @@ impl } } +pub fn get_setup_mandate_body(req: &types::SetupMandateRouterData) -> Result, Error> { + let card_holder_name = req.get_billing_full_name()?; + let auth_details = BamboraapacAuthType::try_from(&req.connector_auth_type)?; + let body = match &req.request.payment_method_data { + domain::PaymentMethodData::Card(card) => { + format!( + r#" + + + + + + + {} + {} + {} + {} + 2 + {} + {} + + ]]> + + + + + "#, + card.card_number.get_card_no(), + card.card_exp_month.peek(), + card.get_expiry_year_4_digit().peek(), + card_holder_name.peek(), + auth_details.username.peek(), + auth_details.password.peek(), + ) + } + _ => { + return Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Bambora APAC"), + ))?; + } + }; + + Ok(body.as_bytes().to_vec()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename = "Envelope")] +#[serde(rename_all = "PascalCase")] +pub struct BamboraapacMandateResponse { + body: MandateBodyResponse, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct MandateBodyResponse { + tokenise_credit_card_response: TokeniseCreditCardResponse, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct TokeniseCreditCardResponse { + tokenise_credit_card_result: TokeniseCreditCardResult, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct TokeniseCreditCardResult { + tokenise_credit_card_response: MandateResponseBody, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct MandateResponseBody { + return_value: u8, + token: Option, +} + +impl + TryFrom< + types::ResponseRouterData< + F, + BamboraapacMandateResponse, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + BamboraapacMandateResponse, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + ) -> Result { + let response_code = item + .response + .body + .tokenise_credit_card_response + .tokenise_credit_card_result + .tokenise_credit_card_response + .return_value; + + let connector_mandate_id = item + .response + .body + .tokenise_credit_card_response + .tokenise_credit_card_result + .tokenise_credit_card_response + .token + .ok_or(errors::ConnectorError::MissingConnectorMandateID)?; + + // transaction approved + if response_code == 0 { + Ok(Self { + status: enums::AttemptStatus::Charged, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: None, + mandate_reference: Some(types::MandateReference { + connector_mandate_id: Some(connector_mandate_id), + payment_method_id: None, + }), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + // transaction failed + else { + Ok(Self { + status: enums::AttemptStatus::Failure, + response: Err(types::ErrorResponse { + status_code: item.http_code, + code: consts::NO_ERROR_CODE.to_string(), + message: consts::NO_ERROR_MESSAGE.to_string(), + reason: None, + attempt_status: None, + connector_transaction_id: None, + }), + ..item.data + }) + } + } +} + // capture body in soap format pub fn get_capture_body( req: &BamboraapacRouterData<&types::PaymentsCaptureRouterData>, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index f080fd550d..b09958f02f 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -80,7 +80,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" applepay.base_url = "https://apple-pay-gateway.apple.com/" 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/dts.asmx" +bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" bankofamerica.base_url = "https://apitest.merchant-services.bankofamerica.com/" billwerk.base_url = "https://api.reepay.com/" billwerk.secondary_base_url = "https://card.reepay.com/"