Skip to content

Commit

Permalink
feat(connector): [BANKOFAMERICA] Implement 3DS flow for cards (#3343)
Browse files Browse the repository at this point in the history
  • Loading branch information
deepanshu-iiitu authored Jan 16, 2024
1 parent eaa8791 commit d533c98
Show file tree
Hide file tree
Showing 8 changed files with 1,017 additions and 16 deletions.
1 change: 1 addition & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ stripe = { payment_method = "bank_transfer" }
nuvei = { payment_method = "card" }
shift4 = { payment_method = "card" }
bluesnap = { payment_method = "card" }
bankofamerica = {payment_method = "card"}
cybersource = {payment_method = "card"}
nmi = {payment_method = "card"}

Expand Down
1 change: 1 addition & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ stripe = {payment_method = "bank_transfer"}
nuvei = {payment_method = "card"}
shift4 = {payment_method = "card"}
bluesnap = {payment_method = "card"}
bankofamerica = {payment_method = "card"}
cybersource = {payment_method = "card"}
nmi = {payment_method = "card"}

Expand Down
1 change: 1 addition & 0 deletions config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ stripe = {payment_method = "bank_transfer"}
nuvei = {payment_method = "card"}
shift4 = {payment_method = "card"}
bluesnap = {payment_method = "card"}
bankofamerica = {payment_method = "card"}
cybersource = {payment_method = "card"}
nmi = {payment_method = "card"}

Expand Down
260 changes: 252 additions & 8 deletions crates/router/src/connector/bankofamerica.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use time::OffsetDateTime;
use transformers as bankofamerica;
use url::Url;

use super::utils::{PaymentsAuthorizeRequestData, RouterData};
use crate::{
configs::settings,
connector::{utils as connector_utils, utils::RefundsRequestData},
Expand Down Expand Up @@ -48,6 +49,8 @@ impl api::Refund for Bankofamerica {}
impl api::RefundExecute for Bankofamerica {}
impl api::RefundSync for Bankofamerica {}
impl api::PaymentToken for Bankofamerica {}
impl api::PaymentsPreProcessing for Bankofamerica {}
impl api::PaymentsCompleteAuthorize for Bankofamerica {}

impl Bankofamerica {
pub fn generate_digest(&self, payload: &[u8]) -> String {
Expand Down Expand Up @@ -299,6 +302,113 @@ impl
}
}

impl
ConnectorIntegration<
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Bankofamerica
{
fn get_headers(
&self,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let redirect_response = req.request.redirect_response.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "redirect_response",
},
)?;
match redirect_response.params {
Some(param) if !param.clone().peek().is_empty() => Ok(format!(
"{}risk/v1/authentications",
self.base_url(connectors)
)),
Some(_) | None => Ok(format!(
"{}risk/v1/authentication-results",
self.base_url(connectors)
)),
}
}
fn get_request_body(
&self,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = bankofamerica::BankOfAmericaRouterData::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 =
bankofamerica::BankOfAmericaPreProcessingRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
&self,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.set_body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(),
))
}

fn handle_response(
&self,
data: &types::PaymentsPreProcessingRouterData,
res: Response,
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaPreProcessingResponse = res
.response
.parse_struct("BankOfAmerica AuthEnrollmentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}

fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}

impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Bankofamerica
{
Expand All @@ -316,13 +426,17 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P

fn get_url(
&self,
_req: &types::PaymentsAuthorizeRouterData,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}pts/v2/payments/",
api::ConnectorCommon::base_url(self, connectors)
))
if req.is_three_ds() && req.request.is_card() {
Ok(format!(
"{}risk/v1/authentication-setups",
self.base_url(connectors)
))
} else {
Ok(format!("{}pts/v2/payments/", self.base_url(connectors)))
}
}

fn get_request_body(
Expand All @@ -336,9 +450,15 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req.request.amount,
req,
))?;
let connector_req =
bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
if req.is_three_ds() && req.request.is_card() {
let connector_req =
bankofamerica::BankOfAmericaAuthSetupRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
} else {
let connector_req =
bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
}

fn build_request(
Expand Down Expand Up @@ -368,6 +488,130 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
data: &types::PaymentsAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
if data.is_three_ds() && data.request.is_card() {
let response: bankofamerica::BankOfAmericaAuthSetupResponse = res
.response
.parse_struct("Bankofamerica AuthSetupResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
} else {
let response: bankofamerica::BankOfAmericaPaymentsResponse = res
.response
.parse_struct("Bankofamerica PaymentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
}

fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}

fn get_5xx_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaServerErrorResponse = res
.response
.parse_struct("BankOfAmericaServerErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
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(consts::NO_ERROR_CODE.to_string()),
message: response
.message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
attempt_status,
connector_transaction_id: None,
})
}
}

impl
ConnectorIntegration<
api::CompleteAuthorize,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
> for Bankofamerica
{
fn get_headers(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}pts/v2/payments/", self.base_url(connectors)))
}
fn get_request_body(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from((
&self.get_currency_unit(),
req.request.currency,
req.request.amount,
req,
))?;
let connector_req =
bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsCompleteAuthorizeType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
self, req, connectors,
)?)
.set_body(types::PaymentsCompleteAuthorizeType::get_request_body(
self, req, connectors,
)?)
.build(),
))
}

fn handle_response(
&self,
data: &types::PaymentsCompleteAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaPaymentsResponse = res
.response
.parse_struct("BankOfAmerica PaymentResponse")
Expand Down
Loading

0 comments on commit d533c98

Please sign in to comment.