Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(connector): [Adyen] ErrorHandling in case of Balance Check for Gift Cards #1976

Merged
merged 13 commits into from
Nov 28, 2023
Merged
153 changes: 53 additions & 100 deletions crates/router/src/connector/adyen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ use crate::{
configs::settings,
connector::utils as connector_utils,
consts,
core::{
self,
errors::{self, CustomResult},
},
headers, logger, routes,
core::errors::{self, CustomResult},
headers, logger,
services::{
self,
request::{self, Mask},
Expand Down Expand Up @@ -541,57 +538,13 @@ impl
}
}

#[async_trait::async_trait]
impl
services::ConnectorIntegration<
api::Authorize,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> for Adyen
{
async fn execute_pretasks(
&self,
router_data: &mut types::PaymentsAuthorizeRouterData,
app_state: &routes::AppState,
) -> CustomResult<(), errors::ConnectorError> {
match &router_data.request.payment_method_data {
api_models::payments::PaymentMethodData::GiftCard(gift_card_data) => {
match gift_card_data.as_ref() {
api_models::payments::GiftCardData::Givex(_) => {
let integ: Box<
&(dyn services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);

let authorize_data = &types::PaymentsBalanceRouterData::from((
&router_data.to_owned(),
router_data.request.clone(),
));

let resp = services::execute_connector_processing_step(
app_state,
integ,
authorize_data,
core::payments::CallConnectorAction::Trigger,
None,
)
.await?;
router_data.payment_method_balance = resp.payment_method_balance;

Ok(())
}
_ => Ok(()),
}
}
_ => Ok(()),
}
}

fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
Expand Down Expand Up @@ -647,7 +600,6 @@ impl
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
check_for_payment_method_balance(req)?;
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
Expand Down Expand Up @@ -701,28 +653,23 @@ impl
}
}

impl api::PaymentsPreProcessing for Adyen {}

impl
services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Adyen
{
fn get_headers(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError>
where
Self: services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
{
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsBalanceType::get_content_type(self)
types::PaymentsPreProcessingType::get_content_type(self)
.to_string()
.into(),
)];
Expand All @@ -733,7 +680,7 @@ impl

fn get_url(
&self,
_req: &types::PaymentsBalanceRouterData,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
Expand All @@ -744,7 +691,7 @@ impl

fn get_request_body(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let connector_req = adyen::AdyenBalanceRequest::try_from(req)?;

Expand All @@ -758,37 +705,67 @@ impl

fn build_request(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsBalanceType::get_url(self, req, connectors)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsBalanceType::get_headers(
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsBalanceType::get_request_body(self, req)?)
.body(types::PaymentsPreProcessingType::get_request_body(
self, req,
)?)
.build(),
))
}

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

let currency = match data.request.currency {
Some(currency) => currency.to_string(),
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?,
};
let amount = match data.request.amount {
Some(amount) => amount,
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?,
};

if response.balance.currency != currency || response.balance.value < amount {
Ok(types::RouterData {
response: Err(types::ErrorResponse {
code: consts::NO_ERROR_CODE.to_string(),
message: consts::LOW_BALANCE_ERROR_MESSAGE.to_string(),
Sakilmostak marked this conversation as resolved.
Show resolved Hide resolved
reason: None,
Sakilmostak marked this conversation as resolved.
Show resolved Hide resolved
status_code: res.status_code,
}),
..data.clone()
})
} else {
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}

fn get_error_response(
Expand Down Expand Up @@ -1598,27 +1575,3 @@ impl api::IncomingWebhook for Adyen {
})
}
}

pub fn check_for_payment_method_balance(
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<(), errors::ConnectorError> {
match &req.request.payment_method_data {
api_models::payments::PaymentMethodData::GiftCard(gift_card) => match gift_card.as_ref() {
api_models::payments::GiftCardData::Givex(_) => {
let payment_method_balance = req
.payment_method_balance
.as_ref()
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
if payment_method_balance.currency != req.request.currency.to_string()
|| payment_method_balance.amount < req.request.amount
{
Err(errors::ConnectorError::InSufficientBalanceInPaymentMethod.into())
} else {
Ok(())
}
}
_ => Ok(()),
},
_ => Ok(()),
}
}
32 changes: 22 additions & 10 deletions crates/router/src/connector/adyen/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ pub struct AdyenBalanceRequest<'a> {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenBalanceResponse {
psp_reference: String,
balance: Amount,
pub psp_reference: String,
pub balance: Amount,
}

/// This implementation will be used only in Authorize, Automatic capture flow.
Expand Down Expand Up @@ -397,8 +397,8 @@ pub enum ActionType {

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Amount {
currency: String,
value: i64,
pub currency: String,
Sakilmostak marked this conversation as resolved.
Show resolved Hide resolved
pub value: i64,
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -1391,11 +1391,11 @@ impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>>
}
}

impl<'a> TryFrom<&types::PaymentsBalanceRouterData> for AdyenBalanceRequest<'a> {
impl<'a> TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'a> {
type Error = Error;
fn try_from(item: &types::PaymentsBalanceRouterData) -> Result<Self, Self::Error> {
fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
let payment_method = match &item.request.payment_method_data {
payments::PaymentMethodData::GiftCard(gift_card_data) => {
Some(payments::PaymentMethodData::GiftCard(gift_card_data)) => {
match gift_card_data.as_ref() {
payments::GiftCardData::Givex(gift_card_data) => {
let balance_pm = BalancePmData {
Expand Down Expand Up @@ -2849,12 +2849,24 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<AdyenCancelResponse>>
}
}

impl TryFrom<types::PaymentsBalanceResponseRouterData<AdyenBalanceResponse>>
for types::PaymentsBalanceRouterData
impl<F>
TryFrom<
types::ResponseRouterData<
F,
AdyenBalanceResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsPreProcessingData, types::PaymentsResponseData>
{
type Error = Error;
fn try_from(
item: types::PaymentsBalanceResponseRouterData<AdyenBalanceResponse>,
item: types::ResponseRouterData<
F,
AdyenBalanceResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
pub(crate) const NO_ERROR_MESSAGE: &str = "No error message";
pub(crate) const NO_ERROR_CODE: &str = "No error code";
pub(crate) const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type";
pub(crate) const LOW_BALANCE_ERROR_MESSAGE: &str = "Balance in the payment method is low";
Sakilmostak marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector";

// General purpose base64 engines
Expand Down
11 changes: 11 additions & 0 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,17 @@ where
(router_data, should_continue_payment)
}
}
Some(api_models::payments::PaymentMethodData::GiftCard(_)) => {
if connector.connector_name == router_types::Connector::Adyen {
router_data = router_data.preprocessing_steps(state, connector).await?;

let is_error_in_response = router_data.response.is_err();
// If is_error_in_response is true, should_continue_payment should be false, we should throw the error
(router_data, !is_error_in_response)
} else {
(router_data, should_continue_payment)
}
}
Some(api_models::payments::PaymentMethodData::BankDebit(_)) => {
if connector.connector_name == router_types::Connector::Gocardless {
router_data = router_data.preprocessing_steps(state, connector).await?;
Expand Down
1 change: 0 additions & 1 deletion crates/router/src/core/payments/flows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,6 @@ impl<const T: u8>

default_imp_for_pre_processing_steps!(
connector::Aci,
connector::Adyen,
connector::Airwallex,
connector::Authorizedotnet,
connector::Bambora,
Expand Down