mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 19:42:27 +08:00
feat(connector): [XENDIT] Added Integrity Check for Authorize, Capture, Refund & RSync flows (#8049)
Co-authored-by: Sayak Bhattacharya <sayak.b@Sayak-Bhattacharya-G092THXJ34.local> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9f9fef492b
commit
caa0723502
@ -57,6 +57,7 @@ use transformers::{self as xendit, XenditEventType, XenditWebhookEvent};
|
||||
use crate::{
|
||||
constants::headers,
|
||||
types::ResponseRouterData,
|
||||
utils as connector_utils,
|
||||
utils::{self, PaymentMethodDataType, RefundsRequestData},
|
||||
};
|
||||
|
||||
@ -301,14 +302,27 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
|
||||
.response
|
||||
.parse_struct("XenditPaymentResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let response_integrity_object = connector_utils::get_authorise_integrity_object(
|
||||
self.amount_converter,
|
||||
response.amount,
|
||||
response.currency.to_string().clone(),
|
||||
)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
|
||||
RouterData::try_from(ResponseRouterData {
|
||||
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed);
|
||||
|
||||
new_router_data.map(|mut router_data| {
|
||||
router_data.request.integrity_object = Some(response_integrity_object);
|
||||
router_data
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
@ -478,20 +492,38 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Xen
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: xendit::XenditResponse =
|
||||
res.response
|
||||
.parse_struct("xendit XenditResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
let response: xendit::XenditResponse = res
|
||||
.response
|
||||
.clone()
|
||||
.parse_struct("xendit XenditResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let response_integrity_object = match response.clone() {
|
||||
xendit::XenditResponse::Payment(p) => connector_utils::get_sync_integrity_object(
|
||||
self.amount_converter,
|
||||
p.amount,
|
||||
p.currency.to_string().clone(),
|
||||
),
|
||||
xendit::XenditResponse::Webhook(p) => connector_utils::get_sync_integrity_object(
|
||||
self.amount_converter,
|
||||
p.data.amount,
|
||||
p.data.currency.to_string().clone(),
|
||||
),
|
||||
};
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
|
||||
RouterData::try_from(ResponseRouterData {
|
||||
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
});
|
||||
new_router_data.and_then(|mut router_data| {
|
||||
let integrity_object = response_integrity_object?;
|
||||
router_data.request.integrity_object = Some(integrity_object);
|
||||
Ok(router_data)
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -599,15 +631,26 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo
|
||||
.parse_struct("Xendit PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let response_integrity_object = connector_utils::get_capture_integrity_object(
|
||||
self.amount_converter,
|
||||
Some(response.amount),
|
||||
response.currency.to_string().clone(),
|
||||
)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
|
||||
RouterData::try_from(ResponseRouterData {
|
||||
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed);
|
||||
|
||||
new_router_data.map(|mut router_data| {
|
||||
router_data.request.integrity_object = Some(response_integrity_object);
|
||||
router_data
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
@ -695,15 +738,27 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Xendit
|
||||
.parse_struct("xendit RefundResponse")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
let response_integrity_object = connector_utils::get_refund_integrity_object(
|
||||
self.amount_converter,
|
||||
response.amount,
|
||||
response.currency.to_string().clone(),
|
||||
)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
|
||||
RouterData::try_from(ResponseRouterData {
|
||||
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
});
|
||||
|
||||
new_router_data
|
||||
.map(|mut router_data| {
|
||||
router_data.request.integrity_object = Some(response_integrity_object);
|
||||
router_data
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
@ -772,19 +827,32 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Xendit {
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
|
||||
let response: xendit::RefundResponse =
|
||||
res.response
|
||||
.parse_struct("xendit RefundResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
let response: xendit::RefundResponse = res
|
||||
.response
|
||||
.clone()
|
||||
.parse_struct("xendit RefundResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
let response_integrity_object = connector_utils::get_refund_integrity_object(
|
||||
self.amount_converter,
|
||||
response.amount,
|
||||
response.currency.to_string().clone(),
|
||||
)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
RouterData::try_from(ResponseRouterData {
|
||||
let new_router_data = RouterData::try_from(ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
});
|
||||
|
||||
new_router_data
|
||||
.map(|mut router_data| {
|
||||
router_data.request.integrity_object = Some(response_integrity_object);
|
||||
router_data
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use cards::CardNumber;
|
||||
use common_enums::enums;
|
||||
use common_enums::{enums, Currency};
|
||||
use common_utils::{pii, request::Method, types::FloatMajorUnit};
|
||||
use hyperswitch_domain_models::{
|
||||
payment_method_data::PaymentMethodData,
|
||||
@ -65,7 +65,7 @@ pub struct CardPaymentRequest {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MandatePaymentRequest {
|
||||
pub amount: FloatMajorUnit,
|
||||
pub currency: common_enums::Currency,
|
||||
pub currency: Currency,
|
||||
pub capture_method: String,
|
||||
pub payment_method_id: Secret<String>,
|
||||
pub channel_properties: ChannelProperties,
|
||||
@ -81,7 +81,7 @@ pub struct XenditPaymentsCaptureRequest {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct XenditPaymentsRequest {
|
||||
pub amount: FloatMajorUnit,
|
||||
pub currency: common_enums::Currency,
|
||||
pub currency: Currency,
|
||||
pub capture_method: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub payment_method: Option<PaymentMethod>,
|
||||
@ -97,7 +97,7 @@ pub struct XenditSplitRoute {
|
||||
pub flat_amount: Option<FloatMajorUnit>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub percent_amount: Option<i64>,
|
||||
pub currency: enums::Currency,
|
||||
pub currency: Currency,
|
||||
pub destination_account_id: String,
|
||||
pub reference_id: String,
|
||||
}
|
||||
@ -164,7 +164,7 @@ pub enum PaymentStatus {
|
||||
AwaitingCapture,
|
||||
Verified,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum XenditResponse {
|
||||
Payment(XenditPaymentResponse),
|
||||
@ -178,6 +178,8 @@ pub struct XenditPaymentResponse {
|
||||
pub payment_method: PaymentMethodInfo,
|
||||
pub failure_code: Option<String>,
|
||||
pub reference_id: Secret<String>,
|
||||
pub amount: FloatMajorUnit,
|
||||
pub currency: Currency,
|
||||
}
|
||||
|
||||
fn map_payment_response_to_attempt_status(
|
||||
@ -502,7 +504,7 @@ impl<F>
|
||||
common_utils::types::AmountConvertor::convert_back(
|
||||
&required_conversion_type,
|
||||
amount,
|
||||
item.data.request.currency.unwrap_or(enums::Currency::USD),
|
||||
item.data.request.currency.unwrap_or(Currency::USD),
|
||||
)
|
||||
.map_err(|_| {
|
||||
errors::ConnectorError::RequestEncodingFailedWithReason(
|
||||
@ -660,7 +662,7 @@ impl TryFrom<&PaymentsPreProcessingRouterData> for XenditSplitRequestData {
|
||||
common_utils::types::AmountConvertor::convert(
|
||||
&required_conversion_type,
|
||||
amount,
|
||||
item.request.currency.unwrap_or(enums::Currency::USD),
|
||||
item.request.currency.unwrap_or(Currency::USD),
|
||||
)
|
||||
.map_err(|_| {
|
||||
errors::ConnectorError::RequestEncodingFailedWithReason(
|
||||
@ -739,8 +741,10 @@ impl From<RefundStatus> for enums::RefundStatus {
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RefundResponse {
|
||||
id: String,
|
||||
status: RefundStatus,
|
||||
pub id: String,
|
||||
pub status: RefundStatus,
|
||||
pub amount: FloatMajorUnit,
|
||||
pub currency: String,
|
||||
}
|
||||
|
||||
impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
|
||||
@ -777,20 +781,22 @@ impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouter
|
||||
pub struct XenditMetadata {
|
||||
pub for_user_id: String,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct XenditWebhookEvent {
|
||||
pub event: XenditEventType,
|
||||
pub data: EventDetails,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct EventDetails {
|
||||
pub id: String,
|
||||
pub payment_request_id: Option<String>,
|
||||
pub amount: FloatMajorUnit,
|
||||
pub currency: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum XenditEventType {
|
||||
#[serde(rename = "payment.succeeded")]
|
||||
PaymentSucceeded,
|
||||
|
||||
@ -6309,6 +6309,74 @@ pub fn get_card_details(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_authorise_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
amount: T,
|
||||
currency: String,
|
||||
) -> Result<AuthoriseIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?;
|
||||
|
||||
Ok(AuthoriseIntegrityObject {
|
||||
amount: amount_in_minor_unit,
|
||||
currency: currency_enum,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_sync_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
amount: T,
|
||||
currency: String,
|
||||
) -> Result<SyncIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
let amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?;
|
||||
|
||||
Ok(SyncIntegrityObject {
|
||||
amount: Some(amount_in_minor_unit),
|
||||
currency: Some(currency_enum),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_capture_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
capture_amount: Option<T>,
|
||||
currency: String,
|
||||
) -> Result<CaptureIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let capture_amount_in_minor_unit = capture_amount
|
||||
.map(|amount| convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum))
|
||||
.transpose()?;
|
||||
|
||||
Ok(CaptureIntegrityObject {
|
||||
capture_amount: capture_amount_in_minor_unit,
|
||||
currency: currency_enum,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_refund_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
refund_amount: T,
|
||||
currency: String,
|
||||
) -> Result<RefundIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let refund_amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, refund_amount, currency_enum)?;
|
||||
|
||||
Ok(RefundIntegrityObject {
|
||||
currency: currency_enum,
|
||||
refund_amount: refund_amount_in_minor_unit,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
pub trait FraudCheckSaleRequest {
|
||||
fn get_order_details(&self) -> Result<Vec<OrderDetailsWithAmount>, Error>;
|
||||
@ -6366,71 +6434,3 @@ impl SplitPaymentData for SetupMandateRequestData {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_refund_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
refund_amount: T,
|
||||
currency: String,
|
||||
) -> Result<RefundIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let refund_amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, refund_amount, currency_enum)?;
|
||||
|
||||
Ok(RefundIntegrityObject {
|
||||
currency: currency_enum,
|
||||
refund_amount: refund_amount_in_minor_unit,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_capture_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
capture_amount: Option<T>,
|
||||
currency: String,
|
||||
) -> Result<CaptureIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let capture_amount_in_minor_unit = capture_amount
|
||||
.map(|amount| convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum))
|
||||
.transpose()?;
|
||||
|
||||
Ok(CaptureIntegrityObject {
|
||||
capture_amount: capture_amount_in_minor_unit,
|
||||
currency: currency_enum,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_sync_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
amount: T,
|
||||
currency: String,
|
||||
) -> Result<SyncIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
let amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?;
|
||||
|
||||
Ok(SyncIntegrityObject {
|
||||
amount: Some(amount_in_minor_unit),
|
||||
currency: Some(currency_enum),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_authorise_integrity_object<T>(
|
||||
amount_convertor: &dyn AmountConvertor<Output = T>,
|
||||
amount: T,
|
||||
currency: String,
|
||||
) -> Result<AuthoriseIntegrityObject, error_stack::Report<errors::ConnectorError>> {
|
||||
let currency_enum = enums::Currency::from_str(currency.to_uppercase().as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
|
||||
let amount_in_minor_unit =
|
||||
convert_back_amount_to_minor_units(amount_convertor, amount, currency_enum)?;
|
||||
|
||||
Ok(AuthoriseIntegrityObject {
|
||||
amount: amount_in_minor_unit,
|
||||
currency: currency_enum,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2750,7 +2750,6 @@ pub fn convert_back_amount_to_minor_units<T>(
|
||||
.convert_back(amount, currency)
|
||||
.change_context(errors::ConnectorError::AmountConversionFailed)
|
||||
}
|
||||
|
||||
pub trait NetworkTokenData {
|
||||
fn get_card_issuer(&self) -> Result<CardIssuer, Error>;
|
||||
fn get_expiry_year_4_digit(&self) -> Secret<String>;
|
||||
|
||||
Reference in New Issue
Block a user