fix(connector): [BANKOFAMERICA] Remove cards 3ds flow (#5294)

This commit is contained in:
DEEPANSHU BANSAL
2024-07-11 18:04:25 +05:30
committed by GitHub
parent 4e41827ade
commit 7c408aff1e
4 changed files with 29 additions and 1097 deletions

View File

@ -12,7 +12,6 @@ use time::OffsetDateTime;
use transformers as bankofamerica;
use url::Url;
use super::utils::{PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData};
use crate::{
configs::settings,
connector::{
@ -53,8 +52,6 @@ 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 {
@ -339,33 +336,18 @@ impl
}
fn get_url(
&self,
req: &types::SetupMandateRouterData,
_req: &types::SetupMandateRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
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)))
}
Ok(format!("{}pts/v2/payments/", self.base_url(connectors)))
}
fn get_request_body(
&self,
req: &types::SetupMandateRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
if req.is_three_ds() && req.request.is_card() {
let connector_req = bankofamerica::BankOfAmericaAuthSetupRequest::try_from((
&req.request.payment_method_data,
req.connector_request_reference_id.clone(),
))?;
Ok(RequestContent::Json(Box::new(connector_req)))
} else {
let connector_req = bankofamerica::BankOfAmericaPaymentsRequest::try_from(req)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
let connector_req = bankofamerica::BankOfAmericaPaymentsRequest::try_from(req)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
@ -395,33 +377,19 @@ impl
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::SetupMandateRouterData, 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)?;
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,
})
} else {
let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res
.response
.parse_struct("BankOfAmericaSetupMandatesResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let response: bankofamerica::BankOfAmericaSetupMandatesResponse = res
.response
.parse_struct("BankOfAmericaSetupMandatesResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
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,
})
}
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}
fn get_error_response(
@ -465,117 +433,6 @@ 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,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaPreProcessingResponse = res
.response
.parse_struct("BankOfAmerica AuthEnrollmentResponse")
.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<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Bankofamerica
{
@ -593,17 +450,13 @@ 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> {
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)))
}
Ok(format!(
"{}pts/v2/payments/",
ConnectorCommon::base_url(self, connectors)
))
}
fn get_request_body(
@ -617,17 +470,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req.request.amount,
req,
))?;
if req.is_three_ds() && req.request.is_card() {
let connector_req = bankofamerica::BankOfAmericaAuthSetupRequest::try_from((
&req.request.payment_method_data,
req.connector_request_reference_id.clone(),
))?;
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)))
}
let connector_req =
bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
}
fn build_request(
@ -658,144 +503,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
event_builder: Option<&mut ConnectorEvent>,
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)?;
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,
})
} else {
let response: bankofamerica::BankOfAmericaPaymentsResponse = res
.response
.parse_struct("Bankofamerica PaymentResponse")
.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<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res, event_builder)
}
fn get_5xx_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaServerErrorResponse = res
.response
.parse_struct("BankOfAmericaServerErrorResponse")
.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(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,
event_builder: Option<&mut ConnectorEvent>,
res: Response,
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
let response: bankofamerica::BankOfAmericaPaymentsResponse = res
.response
.parse_struct("BankOfAmerica PaymentResponse")
.parse_struct("Bankofamerica PaymentResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);

View File

@ -1,7 +1,6 @@
use api_models::payments;
use base64::Engine;
use common_utils::{ext_traits::ValueExt, pii};
use error_stack::ResultExt;
use common_utils::pii;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -9,13 +8,11 @@ use serde_json::Value;
use crate::{
connector::utils::{
self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer,
PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
PaymentsPreProcessingData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData,
PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData,
RecurringMandateData, RouterData,
},
consts,
core::errors,
services,
types::{
self,
api::{self, enums as api_enums},
@ -578,28 +575,6 @@ impl
}
}
impl
From<(
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
BillTo,
)> for OrderInformationWithBill
{
fn from(
(item, bill_to): (
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
BillTo,
),
) -> Self {
Self {
amount_details: Amount {
total_amount: item.amount.to_owned(),
currency: item.router_data.request.currency,
},
bill_to: Some(bill_to),
}
}
}
impl
TryFrom<(
&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
@ -678,67 +653,6 @@ impl
}
}
impl
From<(
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
Option<PaymentSolution>,
&BankOfAmericaConsumerAuthValidateResponse,
)> for ProcessingInformation
{
fn from(
(item, solution, three_ds_data): (
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
Option<PaymentSolution>,
&BankOfAmericaConsumerAuthValidateResponse,
),
) -> Self {
let (action_list, action_token_types, authorization_options) =
if is_customer_initiated_mandate_payment(&item.router_data.request) {
(
Some(vec![BankOfAmericaActionsList::TokenCreate]),
Some(vec![
BankOfAmericaActionsTokenType::PaymentInstrument,
BankOfAmericaActionsTokenType::Customer,
]),
Some(BankOfAmericaAuthorizationOptions {
initiator: Some(BankOfAmericaPaymentInitiator {
initiator_type: Some(BankOfAmericaPaymentInitiatorTypes::Customer),
credential_stored_on_file: Some(true),
stored_credential_used: None,
}),
merchant_intitiated_transaction: None,
}),
)
} else {
(None, None, None)
};
let is_setup_mandate_payment = is_setup_mandate_payment(&item.router_data.request);
let capture = if is_setup_mandate_payment {
Some(false)
} else {
Some(matches!(
item.router_data.request.capture_method,
Some(enums::CaptureMethod::Automatic) | None
))
};
Self {
capture,
payment_solution: solution.map(String::from),
action_list,
action_token_types,
authorization_options,
capture_options: None,
commerce_indicator: three_ds_data
.indicator
.to_owned()
.unwrap_or(String::from("internet")),
}
}
}
impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
for ClientReferenceInformation
{
@ -749,16 +663,6 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
}
}
impl From<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
for ClientReferenceInformation
{
fn from(item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>) -> Self {
Self {
code: Some(item.router_data.connector_request_reference_id.clone()),
}
}
}
impl From<&types::SetupMandateRouterData> for ClientReferenceInformation {
fn from(item: &types::SetupMandateRouterData) -> Self {
Self {
@ -894,71 +798,6 @@ pub struct Avs {
code_raw: Option<String>,
}
impl
TryFrom<(
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
domain::Card,
)> for BankOfAmericaPaymentsRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(item, ccard): (
&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
domain::Card,
),
) -> Result<Self, Self::Error> {
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, bill_to));
let payment_information = PaymentInformation::try_from(&ccard)?;
let client_reference_information = ClientReferenceInformation::from(item);
let three_ds_info: BankOfAmericaThreeDSMetadata = item
.router_data
.request
.connector_meta
.clone()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "connector_meta",
})?
.parse_value("BankOfAmericaThreeDSMetadata")
.change_context(errors::ConnectorError::InvalidConnectorConfig {
config: "metadata",
})?;
let processing_information =
ProcessingInformation::from((item, None, &three_ds_info.three_ds_data));
let consumer_authentication_information = Some(BankOfAmericaConsumerAuthInformation {
ucaf_collection_indicator: three_ds_info.three_ds_data.ucaf_collection_indicator,
cavv: three_ds_info.three_ds_data.cavv,
ucaf_authentication_data: three_ds_info.three_ds_data.ucaf_authentication_data,
xid: three_ds_info.three_ds_data.xid,
directory_server_transaction_id: three_ds_info
.three_ds_data
.directory_server_transaction_id,
specification_version: three_ds_info.three_ds_data.specification_version,
});
let merchant_defined_information = item
.router_data
.request
.metadata
.clone()
.map(Vec::<MerchantDefinedInformation>::foreign_from);
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
consumer_authentication_information,
merchant_defined_information,
})
}
}
impl
TryFrom<(
&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
@ -1245,53 +1084,6 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>>
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaAuthSetupRequest {
payment_information: PaymentInformation,
client_reference_information: ClientReferenceInformation,
}
impl TryFrom<(&domain::PaymentMethodData, String)> for BankOfAmericaAuthSetupRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(payment_method_data, connector_request_reference_id): (&domain::PaymentMethodData, String),
) -> Result<Self, Self::Error> {
match payment_method_data.clone() {
domain::PaymentMethodData::Card(ccard) => {
let payment_information = PaymentInformation::try_from(&ccard)?;
let client_reference_information = ClientReferenceInformation {
code: Some(connector_request_reference_id),
};
Ok(Self {
payment_information,
client_reference_information,
})
}
domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::PayLater(_)
| domain::PaymentMethodData::BankRedirect(_)
| domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward
| domain::PaymentMethodData::RealTimePayment(_)
| domain::PaymentMethodData::Upi(_)
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Bank Of America"),
)
.into())
}
}
}
}
impl
TryFrom<(
&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>,
@ -1399,39 +1191,6 @@ impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus {
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthInformationResponse {
access_token: Secret<String>,
device_data_collection_url: Secret<String>,
reference_id: String,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientAuthSetupInfoResponse {
id: String,
client_reference_information: ClientReferenceInformation,
consumer_authentication_information: BankOfAmericaConsumerAuthInformationResponse,
processor_information: Option<ClientProcessorInformation>,
processing_information: Option<ProcessingInformationResponse>,
payment_account_information: Option<PaymentAccountInformation>,
payment_information: Option<PaymentInformationResponse>,
payment_insights_information: Option<PaymentInsightsInformation>,
risk_information: Option<ClientRiskInformation>,
token_information: Option<BankOfAmericaTokenInformation>,
error_information: Option<BankOfAmericaErrorInformation>,
issuer_information: Option<IssuerInformation>,
reconciliation_id: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum BankOfAmericaAuthSetupResponse {
ClientAuthSetupInfo(Box<ClientAuthSetupInfoResponse>),
ErrorInformation(Box<BankOfAmericaErrorInformationResponse>),
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum BankOfAmericaPaymentsResponse {
@ -1745,533 +1504,6 @@ fn get_payment_response(
}
}
impl<F, T>
TryFrom<
types::ResponseRouterData<
F,
BankOfAmericaAuthSetupResponse,
T,
types::PaymentsResponseData,
>,
> for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
BankOfAmericaAuthSetupResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
match item.response {
BankOfAmericaAuthSetupResponse::ClientAuthSetupInfo(info_response) => Ok(Self {
status: enums::AttemptStatus::AuthenticationPending,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::NoResponseId,
redirection_data: Some(services::RedirectForm::CybersourceAuthSetup {
access_token: info_response
.consumer_authentication_information
.access_token
.expose(),
ddc_url: info_response
.consumer_authentication_information
.device_data_collection_url
.expose(),
reference_id: info_response
.consumer_authentication_information
.reference_id,
}),
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: Some(
info_response
.client_reference_information
.code
.unwrap_or(info_response.id.clone()),
),
incremental_authorization_allowed: None,
charge_id: None,
}),
..item.data
}),
BankOfAmericaAuthSetupResponse::ErrorInformation(error_response) => {
let detailed_error_info =
error_response
.error_information
.to_owned()
.details
.map(|error_details| {
error_details
.iter()
.map(|details| format!("{} : {}", details.field, details.reason))
.collect::<Vec<_>>()
.join(", ")
});
let reason = get_error_reason(
error_response.error_information.message,
detailed_error_info,
None,
);
Ok(Self {
response: Err(types::ErrorResponse {
code: error_response
.error_information
.reason
.clone()
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
message: error_response
.error_information
.reason
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
reason,
status_code: item.http_code,
attempt_status: None,
connector_transaction_id: Some(error_response.id.clone()),
}),
status: enums::AttemptStatus::AuthenticationFailed,
..item.data
})
}
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthInformationRequest {
return_url: String,
reference_id: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaAuthEnrollmentRequest {
payment_information: PaymentInformation,
client_reference_information: ClientReferenceInformation,
consumer_authentication_information: BankOfAmericaConsumerAuthInformationRequest,
order_information: OrderInformationWithBill,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BankOfAmericaRedirectionAuthResponse {
pub transaction_id: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthInformationValidateRequest {
authentication_transaction_id: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaAuthValidateRequest {
payment_information: PaymentInformation,
client_reference_information: ClientReferenceInformation,
consumer_authentication_information: BankOfAmericaConsumerAuthInformationValidateRequest,
order_information: OrderInformation,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum BankOfAmericaPreProcessingRequest {
AuthEnrollment(Box<BankOfAmericaAuthEnrollmentRequest>),
AuthValidate(Box<BankOfAmericaAuthValidateRequest>),
}
impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>>
for BankOfAmericaPreProcessingRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &BankOfAmericaRouterData<&types::PaymentsPreProcessingRouterData>,
) -> Result<Self, Self::Error> {
let client_reference_information = ClientReferenceInformation {
code: Some(item.router_data.connector_request_reference_id.clone()),
};
let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or(
errors::ConnectorError::MissingConnectorRedirectionPayload {
field_name: "payment_method_data",
},
)?;
let payment_information = match payment_method_data {
domain::PaymentMethodData::Card(ccard) => PaymentInformation::try_from(&ccard),
domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::PayLater(_)
| domain::PaymentMethodData::BankRedirect(_)
| domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward
| domain::PaymentMethodData::RealTimePayment(_)
| domain::PaymentMethodData::Upi(_)
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
)
.into())
}
}?;
let redirect_response = item.router_data.request.redirect_response.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "redirect_response",
},
)?;
let amount_details = Amount {
total_amount: item.amount.clone(),
currency: item.router_data.request.currency.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "currency",
},
)?,
};
match redirect_response.params {
Some(param) if !param.clone().peek().is_empty() => {
let reference_id = param
.clone()
.peek()
.split_once('=')
.ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload {
field_name: "request.redirect_response.params.reference_id",
})?
.1
.to_string();
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 {
amount_details,
bill_to: Some(bill_to),
};
Ok(Self::AuthEnrollment(Box::new(
BankOfAmericaAuthEnrollmentRequest {
payment_information,
client_reference_information,
consumer_authentication_information:
BankOfAmericaConsumerAuthInformationRequest {
return_url: item
.router_data
.request
.get_complete_authorize_url()?,
reference_id,
},
order_information,
},
)))
}
Some(_) | None => {
let redirect_payload: BankOfAmericaRedirectionAuthResponse = redirect_response
.payload
.ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload {
field_name: "request.redirect_response.payload",
})?
.peek()
.clone()
.parse_value("BankOfAmericaRedirectionAuthResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let order_information = OrderInformation { amount_details };
Ok(Self::AuthValidate(Box::new(
BankOfAmericaAuthValidateRequest {
payment_information,
client_reference_information,
consumer_authentication_information:
BankOfAmericaConsumerAuthInformationValidateRequest {
authentication_transaction_id: redirect_payload.transaction_id,
},
order_information,
},
)))
}
}
}
}
impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
for BankOfAmericaPaymentsRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &BankOfAmericaRouterData<&types::PaymentsCompleteAuthorizeRouterData>,
) -> Result<Self, Self::Error> {
let payment_method_data = item.router_data.request.payment_method_data.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "payment_method_data",
},
)?;
match payment_method_data {
domain::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::PayLater(_)
| domain::PaymentMethodData::BankRedirect(_)
| domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward
| domain::PaymentMethodData::RealTimePayment(_)
| domain::PaymentMethodData::Upi(_)
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("BankOfAmerica"),
)
.into())
}
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BankOfAmericaAuthEnrollmentStatus {
PendingAuthentication,
AuthenticationSuccessful,
AuthenticationFailed,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthValidateResponse {
ucaf_collection_indicator: Option<String>,
cavv: Option<String>,
ucaf_authentication_data: Option<Secret<String>>,
xid: Option<String>,
specification_version: Option<String>,
directory_server_transaction_id: Option<Secret<String>>,
indicator: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct BankOfAmericaThreeDSMetadata {
three_ds_data: BankOfAmericaConsumerAuthValidateResponse,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BankOfAmericaConsumerAuthInformationEnrollmentResponse {
access_token: Option<Secret<String>>,
step_up_url: Option<String>,
//Added to segregate the three_ds_data in a separate struct
#[serde(flatten)]
validate_response: BankOfAmericaConsumerAuthValidateResponse,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientAuthCheckInfoResponse {
id: String,
client_reference_information: ClientReferenceInformation,
consumer_authentication_information: BankOfAmericaConsumerAuthInformationEnrollmentResponse,
status: BankOfAmericaAuthEnrollmentStatus,
error_information: Option<BankOfAmericaErrorInformation>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum BankOfAmericaPreProcessingResponse {
ClientAuthCheckInfo(Box<ClientAuthCheckInfoResponse>),
ErrorInformation(Box<BankOfAmericaErrorInformationResponse>),
}
impl From<BankOfAmericaAuthEnrollmentStatus> for enums::AttemptStatus {
fn from(item: BankOfAmericaAuthEnrollmentStatus) -> Self {
match item {
BankOfAmericaAuthEnrollmentStatus::PendingAuthentication => Self::AuthenticationPending,
BankOfAmericaAuthEnrollmentStatus::AuthenticationSuccessful => {
Self::AuthenticationSuccessful
}
BankOfAmericaAuthEnrollmentStatus::AuthenticationFailed => Self::AuthenticationFailed,
}
}
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
BankOfAmericaPreProcessingResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsPreProcessingData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
BankOfAmericaPreProcessingResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
match item.response {
BankOfAmericaPreProcessingResponse::ClientAuthCheckInfo(info_response) => {
let status = enums::AttemptStatus::from(info_response.status);
let risk_info: Option<ClientRiskInformation> = None;
if utils::is_payment_failure(status) {
let response = Err(types::ErrorResponse::foreign_from((
&info_response.error_information,
&risk_info,
Some(status),
item.http_code,
info_response.id.clone(),
)));
Ok(Self {
status,
response,
..item.data
})
} else {
let connector_response_reference_id = Some(
info_response
.client_reference_information
.code
.unwrap_or(info_response.id.clone()),
);
let redirection_data = match (
info_response
.consumer_authentication_information
.access_token
.map(|access_token| access_token.expose()),
info_response
.consumer_authentication_information
.step_up_url,
) {
(Some(access_token), Some(step_up_url)) => {
Some(services::RedirectForm::CybersourceConsumerAuth {
access_token,
step_up_url,
})
}
_ => None,
};
let three_ds_data = serde_json::to_value(
info_response
.consumer_authentication_information
.validate_response,
)
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
Ok(Self {
status,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::NoResponseId,
redirection_data,
mandate_reference: None,
connector_metadata: Some(serde_json::json!({
"three_ds_data": three_ds_data
})),
network_txn_id: None,
connector_response_reference_id,
incremental_authorization_allowed: None,
charge_id: None,
}),
..item.data
})
}
}
BankOfAmericaPreProcessingResponse::ErrorInformation(error_response) => {
let response = Err(types::ErrorResponse::foreign_from((
&*error_response,
item.http_code,
)));
Ok(Self {
response,
status: enums::AttemptStatus::AuthenticationFailed,
..item.data
})
}
}
}
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
BankOfAmericaPaymentsResponse,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
BankOfAmericaPaymentsResponse,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
match item.response {
BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => {
let status = enums::AttemptStatus::foreign_from((
info_response.status.clone(),
item.data.request.is_auto_capture()?
|| is_setup_mandate_payment(&item.data.request),
));
let response = get_payment_response((&info_response, status, item.http_code));
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| {
types::AdditionalPaymentMethodConnectorResponse::foreign_from((
processor_information,
consumer_auth_information,
))
})
})
.map(types::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::Upi
| common_enums::PaymentMethod::Voucher
| common_enums::PaymentMethod::GiftCard => None,
};
Ok(Self {
status,
response,
connector_response,
..item.data
})
}
BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => {
Ok(Self::foreign_from((
&*error_response.clone(),
item,
Some(enums::AttemptStatus::Failure),
)))
}
}
}
}
impl<F>
TryFrom<
types::ResponseRouterData<
@ -3318,17 +2550,6 @@ fn get_commerce_indicator(network: Option<String>) -> String {
.to_string()
}
fn is_setup_mandate_payment(item: &types::CompleteAuthorizeData) -> bool {
matches!(item.amount, 0) && is_customer_initiated_mandate_payment(item)
}
fn is_customer_initiated_mandate_payment(item: &types::CompleteAuthorizeData) -> bool {
item.setup_future_usage.map_or(false, |future_usage| {
matches!(future_usage, common_enums::FutureUsage::OffSession)
})
// add check for customer_acceptance
}
pub fn get_error_reason(
error_info: Option<String>,
detailed_error_info: Option<String>,

View File

@ -1975,8 +1975,7 @@ where
router_data = router_data.preprocessing_steps(state, connector).await?;
(router_data, false)
} else if (connector.connector_name == router_types::Connector::Cybersource
|| connector.connector_name == router_types::Connector::Bankofamerica)
} else if connector.connector_name == router_types::Connector::Cybersource
&& is_operation_complete_authorize(&operation)
&& router_data.auth_type == storage_enums::AuthenticationType::ThreeDs
{

View File

@ -166,6 +166,7 @@ default_imp_for_complete_authorize!(
connector::Aci,
connector::Adyen,
connector::Bamboraapac,
connector::Bankofamerica,
connector::Billwerk,
connector::Bitpay,
connector::Boku,
@ -987,6 +988,7 @@ default_imp_for_pre_processing_steps!(
connector::Authorizedotnet,
connector::Bambora,
connector::Bamboraapac,
connector::Bankofamerica,
connector::Billwerk,
connector::Bitpay,
connector::Bluesnap,