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
155 changes: 54 additions & 101 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 @@ -543,57 +540,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 @@ -649,7 +602,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 @@ -703,28 +655,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 @@ -735,7 +682,7 @@ impl

fn get_url(
&self,
_req: &types::PaymentsBalanceRouterData,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
Expand All @@ -746,7 +693,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 @@ -760,37 +707,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,
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::NO_ERROR_MESSAGE.to_string(),
reason: Some(consts::LOW_BALANCE_ERROR_MESSAGE.to_string()),
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 @@ -1591,7 +1568,7 @@ impl api::IncomingWebhook for Adyen {
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api::disputes::DisputePayload {
amount: notif.amount.value.to_string(),
currency: notif.amount.currency,
currency: notif.amount.currency.to_string(),
dispute_stage: api_models::enums::DisputeStage::from(notif.event_code.clone()),
connector_dispute_id: notif.psp_reference,
connector_reason: notif.reason,
Expand All @@ -1603,27 +1580,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(()),
}
}
46 changes: 29 additions & 17 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: storage_enums::Currency,
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 @@ -1509,7 +1509,7 @@ fn get_channel_type(pm_type: &Option<storage_enums::PaymentMethodType>) -> Optio

fn get_amount_data(item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>) -> Amount {
Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.amount.to_owned(),
}
}
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 Expand Up @@ -3439,7 +3451,7 @@ impl TryFrom<&AdyenRouterData<&types::PaymentsCaptureRouterData>> for AdyenCaptu
merchant_account: auth_type.merchant_account,
reference,
amount: Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.amount.to_owned(),
},
})
Expand Down Expand Up @@ -3529,7 +3541,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::RefundsRouterData<F>>> for AdyenRefundR
Ok(Self {
merchant_account: auth_type.merchant_account,
amount: Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.router_data.request.refund_amount,
},
merchant_refund_reason: item.router_data.request.reason.clone(),
Expand Down Expand Up @@ -3611,7 +3623,7 @@ pub struct AdyenAdditionalDataWH {
#[derive(Debug, Deserialize)]
pub struct AdyenAmountWH {
pub value: i64,
pub currency: String,
pub currency: storage_enums::Currency,
}

#[derive(Clone, Debug, Deserialize, Serialize, strum::Display, PartialEq)]
Expand Down Expand Up @@ -3937,7 +3949,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutE
)?;
Ok(Self {
amount: Amount {
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
value: item.amount.to_owned(),
},
merchant_account: auth_type.merchant_account,
Expand Down Expand Up @@ -4008,7 +4020,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutC
Ok(Self {
amount: Amount {
value: item.amount.to_owned(),
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
},
recurring: RecurringContract {
contract: Contract::Payout,
Expand Down Expand Up @@ -4055,7 +4067,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutF
Ok(Self::Card(Box::new(PayoutFulfillCardRequest {
amount: Amount {
value: item.amount.to_owned(),
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
},
card: get_payout_card_details(&item.router_data.get_payout_method_data()?)
.map_or(
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 @@ -23,6 +23,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 = "Insufficient balance in the payment method";
pub(crate) const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector";

// General purpose base64 engines
Expand Down
Loading
Loading