mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
feat(connector): [Adyen] add dispute flows for adyen connector (#5514)
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
pub mod transformers;
|
||||
|
||||
use api_models::{enums::PaymentMethodType, webhooks::IncomingWebhookEvent};
|
||||
use base64::Engine;
|
||||
use common_utils::{
|
||||
@ -37,7 +36,6 @@ use crate::{
|
||||
},
|
||||
utils::{crypto, ByteSliceExt, BytesExt, OptionExt},
|
||||
};
|
||||
|
||||
const ADYEN_API_VERSION: &str = "v68";
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -52,16 +50,13 @@ impl Adyen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Adyen {
|
||||
fn id(&self) -> &'static str {
|
||||
"adyen"
|
||||
}
|
||||
|
||||
fn get_currency_unit(&self) -> api::CurrencyUnit {
|
||||
api::CurrencyUnit::Minor
|
||||
}
|
||||
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
@ -1151,7 +1146,7 @@ impl services::ConnectorIntegration<api::PoCancel, types::PayoutsData, types::Pa
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.secondary_base_url.as_str(),
|
||||
connectors.adyen.payout_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
@ -1255,7 +1250,7 @@ impl services::ConnectorIntegration<api::PoCreate, types::PayoutsData, types::Pa
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.secondary_base_url.as_str(),
|
||||
connectors.adyen.payout_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
@ -1471,7 +1466,7 @@ impl services::ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::P
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.secondary_base_url.as_str(),
|
||||
connectors.adyen.payout_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
@ -1910,3 +1905,337 @@ impl api::IncomingWebhook for Adyen {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Dispute for Adyen {}
|
||||
impl api::DefendDispute for Adyen {}
|
||||
impl api::AcceptDispute for Adyen {}
|
||||
impl api::SubmitEvidence for Adyen {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Accept,
|
||||
types::AcceptDisputeRequestData,
|
||||
types::AcceptDisputeResponse,
|
||||
> for Adyen
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::AcceptDisputeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::AcceptDisputeType::get_content_type(self)
|
||||
.to_string()
|
||||
.into(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::AcceptDisputeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.dispute_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
Ok(format!(
|
||||
"{}ca/services/DisputeService/v30/acceptDispute",
|
||||
endpoint
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::AcceptDisputeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::AcceptDisputeType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::AcceptDisputeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::AcceptDisputeType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::AcceptDisputeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = adyen::AdyenAcceptDisputeRequest::try_from(req)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::AcceptDisputeRouterData,
|
||||
_event_builder: Option<&mut ConnectorEvent>,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::AcceptDisputeRouterData, errors::ConnectorError> {
|
||||
let response: adyen::AdyenDisputeResponse = res
|
||||
.response
|
||||
.parse_struct("AdyenDisputeResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::foreign_try_from((data, response))
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Defend,
|
||||
types::DefendDisputeRequestData,
|
||||
types::DefendDisputeResponse,
|
||||
> for Adyen
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::DefendDisputeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::DefendDisputeType::get_content_type(self)
|
||||
.to_string()
|
||||
.into(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::DefendDisputeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.dispute_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
Ok(format!(
|
||||
"{}ca/services/DisputeService/v30/defendDispute",
|
||||
endpoint
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::DefendDisputeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::DefendDisputeType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::DefendDisputeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::DefendDisputeType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::DefendDisputeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = adyen::AdyenDefendDisputeRequest::try_from(req)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::DefendDisputeRouterData,
|
||||
_event_builder: Option<&mut ConnectorEvent>,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::DefendDisputeRouterData, errors::ConnectorError> {
|
||||
let response: adyen::AdyenDisputeResponse = res
|
||||
.response
|
||||
.parse_struct("AdyenDisputeResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::foreign_try_from((data, response))
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Evidence,
|
||||
types::SubmitEvidenceRequestData,
|
||||
types::SubmitEvidenceResponse,
|
||||
> for Adyen
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::SubmitEvidenceType::get_content_type(self)
|
||||
.to_string()
|
||||
.into(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let endpoint = build_env_specific_endpoint(
|
||||
connectors.adyen.dispute_base_url.as_str(),
|
||||
req.test_mode,
|
||||
&req.connector_meta_data,
|
||||
)?;
|
||||
Ok(format!(
|
||||
"{}ca/services/DisputeService/v30/supplyDefenseDocument",
|
||||
endpoint
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = adyen::Evidence::try_from(req)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::SubmitEvidenceType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::SubmitEvidenceType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::SubmitEvidenceType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::SubmitEvidenceRouterData,
|
||||
_event_builder: Option<&mut ConnectorEvent>,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::SubmitEvidenceRouterData, errors::ConnectorError> {
|
||||
let response: adyen::AdyenDisputeResponse = res
|
||||
.response
|
||||
.parse_struct("AdyenDisputeResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::foreign_try_from((data, response))
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
impl api::UploadFile for Adyen {}
|
||||
impl api::RetrieveFile for Adyen {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Retrieve,
|
||||
types::RetrieveFileRequestData,
|
||||
types::RetrieveFileResponse,
|
||||
> for Adyen
|
||||
{
|
||||
}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Upload,
|
||||
types::UploadFileRequestData,
|
||||
types::UploadFileResponse,
|
||||
> for Adyen
|
||||
{
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl api::FileUpload for Adyen {
|
||||
fn validate_file_upload(
|
||||
&self,
|
||||
purpose: api::FilePurpose,
|
||||
file_size: i32,
|
||||
file_type: mime::Mime,
|
||||
) -> CustomResult<(), errors::ConnectorError> {
|
||||
match purpose {
|
||||
api::FilePurpose::DisputeEvidence => {
|
||||
let supported_file_types =
|
||||
["image/jpeg", "image/jpg", "image/png", "application/pdf"];
|
||||
if !supported_file_types.contains(&file_type.to_string().as_str()) {
|
||||
Err(errors::ConnectorError::FileValidationFailed {
|
||||
reason: "file_type does not match JPEG, JPG, PNG, or PDF format".to_owned(),
|
||||
})?
|
||||
}
|
||||
//10 MB
|
||||
if (file_type.to_string().as_str() == "image/jpeg"
|
||||
|| file_type.to_string().as_str() == "image/jpg"
|
||||
|| file_type.to_string().as_str() == "image/png")
|
||||
&& file_size > 10000000
|
||||
{
|
||||
Err(errors::ConnectorError::FileValidationFailed {
|
||||
reason: "file_size exceeded the max file size of 10MB for Image formats"
|
||||
.to_owned(),
|
||||
})?
|
||||
}
|
||||
//2 MB
|
||||
if file_type.to_string().as_str() == "application/pdf" && file_size > 2000000 {
|
||||
Err(errors::ConnectorError::FileValidationFailed {
|
||||
reason: "file_size exceeded the max file size of 2MB for PDF formats"
|
||||
.to_owned(),
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ use api_models::{enums, payments, webhooks};
|
||||
use cards::CardNumber;
|
||||
use common_utils::{errors::ParsingError, ext_traits::Encode, id_type, pii, types::MinorUnit};
|
||||
use error_stack::{report, ResultExt};
|
||||
use hyperswitch_domain_models::router_request_types::SubmitEvidenceRequestData;
|
||||
use masking::{ExposeInterface, PeekInterface};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -4860,3 +4861,294 @@ impl From<AdyenStatus> for storage_enums::PayoutStatus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_merchant_account_code(
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> errors::CustomResult<Secret<String>, errors::ConnectorError> {
|
||||
let auth = AdyenAuthType::try_from(auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(auth.merchant_account.clone())
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenAcceptDisputeRequest {
|
||||
dispute_psp_reference: String,
|
||||
merchant_account_code: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenDisputeResponse {
|
||||
pub error_message: Option<String>,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::AcceptDisputeRouterData> for AdyenAcceptDisputeRequest {
|
||||
type Error = Error;
|
||||
fn try_from(item: &types::AcceptDisputeRouterData) -> Result<Self, Self::Error> {
|
||||
let merchant_account_code = get_merchant_account_code(&item.connector_auth_type)?;
|
||||
Ok(Self {
|
||||
dispute_psp_reference: item.clone().request.connector_dispute_id,
|
||||
merchant_account_code,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdyenDefendDisputeRequest {
|
||||
dispute_psp_reference: String,
|
||||
merchant_account_code: Secret<String>,
|
||||
defense_reason_code: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::DefendDisputeRouterData> for AdyenDefendDisputeRequest {
|
||||
type Error = Error;
|
||||
fn try_from(item: &types::DefendDisputeRouterData) -> Result<Self, Self::Error> {
|
||||
let merchant_account_code = get_merchant_account_code(&item.connector_auth_type)?;
|
||||
Ok(Self {
|
||||
dispute_psp_reference: item.request.connector_dispute_id.clone(),
|
||||
merchant_account_code,
|
||||
defense_reason_code: "SupplyDefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
pub struct Evidence {
|
||||
defense_documents: Vec<DefenseDocuments>,
|
||||
merchant_account_code: Secret<String>,
|
||||
dispute_psp_reference: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
pub struct DefenseDocuments {
|
||||
content: Secret<String>,
|
||||
content_type: Option<String>,
|
||||
defense_document_type_code: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::SubmitEvidenceRouterData> for Evidence {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::SubmitEvidenceRouterData) -> Result<Self, Self::Error> {
|
||||
let merchant_account_code = get_merchant_account_code(&item.connector_auth_type)?;
|
||||
let submit_evidence_request_data = item.request.clone();
|
||||
Ok(Self {
|
||||
defense_documents: get_defence_documents(submit_evidence_request_data).ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "Missing Defence Documents",
|
||||
},
|
||||
)?,
|
||||
merchant_account_code,
|
||||
dispute_psp_reference: item.request.connector_dispute_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_defence_documents(item: SubmitEvidenceRequestData) -> Option<Vec<DefenseDocuments>> {
|
||||
let mut defense_documents: Vec<DefenseDocuments> = Vec::new();
|
||||
if let Some(shipping_documentation) = item.shipping_documentation {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(shipping_documentation).into(),
|
||||
content_type: item.receipt_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(receipt) = item.receipt {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(receipt).into(),
|
||||
content_type: item.shipping_documentation_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(invoice_showing_distinct_transactions) = item.invoice_showing_distinct_transactions
|
||||
{
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(invoice_showing_distinct_transactions).into(),
|
||||
content_type: item.invoice_showing_distinct_transactions_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(customer_communication) = item.customer_communication {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(customer_communication).into(),
|
||||
content_type: item.customer_communication_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(refund_policy) = item.refund_policy {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(refund_policy).into(),
|
||||
content_type: item.refund_policy_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(recurring_transaction_agreement) = item.recurring_transaction_agreement {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(recurring_transaction_agreement).into(),
|
||||
content_type: item.recurring_transaction_agreement_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(uncategorized_file) = item.uncategorized_file {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(uncategorized_file).into(),
|
||||
content_type: item.uncategorized_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(cancellation_policy) = item.cancellation_policy {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(cancellation_policy).into(),
|
||||
content_type: item.cancellation_policy_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(customer_signature) = item.customer_signature {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(customer_signature).into(),
|
||||
content_type: item.customer_signature_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
if let Some(service_documentation) = item.service_documentation {
|
||||
defense_documents.push(DefenseDocuments {
|
||||
content: get_content(service_documentation).into(),
|
||||
content_type: item.service_documentation_file_type,
|
||||
defense_document_type_code: "DefenseMaterial".into(),
|
||||
})
|
||||
}
|
||||
|
||||
if defense_documents.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(defense_documents)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_content(item: Vec<u8>) -> String {
|
||||
String::from_utf8_lossy(&item).to_string()
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<(&Self, AdyenDisputeResponse)> for types::AcceptDisputeRouterData {
|
||||
type Error = errors::ConnectorError;
|
||||
|
||||
fn foreign_try_from(item: (&Self, AdyenDisputeResponse)) -> Result<Self, Self::Error> {
|
||||
let (data, response) = item;
|
||||
|
||||
if response.success {
|
||||
Ok(types::AcceptDisputeRouterData {
|
||||
response: Ok(types::AcceptDisputeResponse {
|
||||
dispute_status: api_enums::DisputeStatus::DisputeAccepted,
|
||||
connector_status: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
} else {
|
||||
Ok(types::AcceptDisputeRouterData {
|
||||
response: Err(types::ErrorResponse {
|
||||
code: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: response.error_message,
|
||||
status_code: data.connector_http_status_code.ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "http code",
|
||||
},
|
||||
)?,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<(&Self, AdyenDisputeResponse)> for types::SubmitEvidenceRouterData {
|
||||
type Error = errors::ConnectorError;
|
||||
fn foreign_try_from(item: (&Self, AdyenDisputeResponse)) -> Result<Self, Self::Error> {
|
||||
let (data, response) = item;
|
||||
if response.success {
|
||||
Ok(types::SubmitEvidenceRouterData {
|
||||
response: Ok(types::SubmitEvidenceResponse {
|
||||
dispute_status: api_enums::DisputeStatus::DisputeChallenged,
|
||||
connector_status: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
} else {
|
||||
Ok(types::SubmitEvidenceRouterData {
|
||||
response: Err(types::ErrorResponse {
|
||||
code: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: response.error_message,
|
||||
status_code: data.connector_http_status_code.ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "http code",
|
||||
},
|
||||
)?,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<(&Self, AdyenDisputeResponse)> for types::DefendDisputeRouterData {
|
||||
type Error = errors::ConnectorError;
|
||||
|
||||
fn foreign_try_from(item: (&Self, AdyenDisputeResponse)) -> Result<Self, Self::Error> {
|
||||
let (data, response) = item;
|
||||
|
||||
if response.success {
|
||||
Ok(types::DefendDisputeRouterData {
|
||||
response: Ok(types::DefendDisputeResponse {
|
||||
dispute_status: api_enums::DisputeStatus::DisputeChallenged,
|
||||
connector_status: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
} else {
|
||||
Ok(types::DefendDisputeRouterData {
|
||||
response: Err(types::ErrorResponse {
|
||||
code: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
|
||||
message: response
|
||||
.error_message
|
||||
.clone()
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: response.error_message,
|
||||
status_code: data.connector_http_status_code.ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "http code",
|
||||
},
|
||||
)?,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,34 +20,31 @@ pub async fn get_evidence_request_data(
|
||||
evidence_request: api_models::disputes::SubmitEvidenceRequest,
|
||||
dispute: &diesel_models::dispute::Dispute,
|
||||
) -> CustomResult<SubmitEvidenceRequestData, errors::ApiErrorResponse> {
|
||||
let (cancellation_policy, cancellation_policy_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.cancellation_policy,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (customer_communication, customer_communication_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.customer_communication,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (customer_signature, customer_signature_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.customer_signature,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (receipt, receipt_provider_file_id) = retrieve_file_and_provider_file_id_from_file_id(
|
||||
let cancellation_policy_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.cancellation_policy,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let customer_communication_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.customer_communication,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let customer_sifnature_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.customer_signature,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let receipt_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.receipt,
|
||||
merchant_account,
|
||||
@ -55,45 +52,40 @@ pub async fn get_evidence_request_data(
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (refund_policy, refund_policy_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.refund_policy,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (service_documentation, service_documentation_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.service_documentation,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (shipping_documentation, shipping_documentation_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.shipping_documentation,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
invoice_showing_distinct_transactions,
|
||||
invoice_showing_distinct_transactions_provider_file_id,
|
||||
) = retrieve_file_and_provider_file_id_from_file_id(
|
||||
let refund_policy_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.invoice_showing_distinct_transactions,
|
||||
evidence_request.refund_policy,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (recurring_transaction_agreement, recurring_transaction_agreement_provider_file_id) =
|
||||
let service_documentation_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.service_documentation,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let shipping_documentation_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.shipping_documentation,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let invoice_showing_distinct_transactions_file_info =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.invoice_showing_distinct_transactions,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let recurring_transaction_agreement_file_info =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.recurring_transaction_agreement,
|
||||
@ -102,54 +94,68 @@ pub async fn get_evidence_request_data(
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let (uncategorized_file, uncategorized_file_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.uncategorized_file,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
let uncategorized_file_info = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.uncategorized_file,
|
||||
merchant_account,
|
||||
key_store,
|
||||
api::FileDataRequired::NotRequired,
|
||||
)
|
||||
.await?;
|
||||
Ok(SubmitEvidenceRequestData {
|
||||
dispute_id: dispute.dispute_id.clone(),
|
||||
connector_dispute_id: dispute.connector_dispute_id.clone(),
|
||||
access_activity_log: evidence_request.access_activity_log,
|
||||
billing_address: evidence_request.billing_address,
|
||||
cancellation_policy,
|
||||
cancellation_policy_provider_file_id,
|
||||
cancellation_policy: cancellation_policy_file_info.file_data,
|
||||
cancellation_policy_provider_file_id: cancellation_policy_file_info.provider_file_id,
|
||||
cancellation_policy_disclosure: evidence_request.cancellation_policy_disclosure,
|
||||
cancellation_rebuttal: evidence_request.cancellation_rebuttal,
|
||||
customer_communication,
|
||||
customer_communication_provider_file_id,
|
||||
customer_communication: customer_communication_file_info.file_data,
|
||||
customer_communication_provider_file_id: customer_communication_file_info.provider_file_id,
|
||||
customer_email_address: evidence_request.customer_email_address,
|
||||
customer_name: evidence_request.customer_name,
|
||||
customer_purchase_ip: evidence_request.customer_purchase_ip,
|
||||
customer_signature,
|
||||
customer_signature_provider_file_id,
|
||||
customer_signature: customer_sifnature_file_info.file_data,
|
||||
customer_signature_provider_file_id: customer_sifnature_file_info.provider_file_id,
|
||||
product_description: evidence_request.product_description,
|
||||
receipt,
|
||||
receipt_provider_file_id,
|
||||
refund_policy,
|
||||
refund_policy_provider_file_id,
|
||||
receipt: receipt_file_info.file_data,
|
||||
receipt_provider_file_id: receipt_file_info.provider_file_id,
|
||||
refund_policy: refund_policy_file_info.file_data,
|
||||
refund_policy_provider_file_id: refund_policy_file_info.provider_file_id,
|
||||
refund_policy_disclosure: evidence_request.refund_policy_disclosure,
|
||||
refund_refusal_explanation: evidence_request.refund_refusal_explanation,
|
||||
service_date: evidence_request.service_date,
|
||||
service_documentation,
|
||||
service_documentation_provider_file_id,
|
||||
service_documentation: service_documentation_file_info.file_data,
|
||||
service_documentation_provider_file_id: service_documentation_file_info.provider_file_id,
|
||||
shipping_address: evidence_request.shipping_address,
|
||||
shipping_carrier: evidence_request.shipping_carrier,
|
||||
shipping_date: evidence_request.shipping_date,
|
||||
shipping_documentation,
|
||||
shipping_documentation_provider_file_id,
|
||||
shipping_documentation: shipping_documentation_file_info.file_data,
|
||||
shipping_documentation_provider_file_id: shipping_documentation_file_info.provider_file_id,
|
||||
shipping_tracking_number: evidence_request.shipping_tracking_number,
|
||||
invoice_showing_distinct_transactions,
|
||||
invoice_showing_distinct_transactions_provider_file_id,
|
||||
recurring_transaction_agreement,
|
||||
recurring_transaction_agreement_provider_file_id,
|
||||
uncategorized_file,
|
||||
uncategorized_file_provider_file_id,
|
||||
invoice_showing_distinct_transactions: invoice_showing_distinct_transactions_file_info
|
||||
.file_data,
|
||||
invoice_showing_distinct_transactions_provider_file_id:
|
||||
invoice_showing_distinct_transactions_file_info.provider_file_id,
|
||||
recurring_transaction_agreement: recurring_transaction_agreement_file_info.file_data,
|
||||
recurring_transaction_agreement_provider_file_id: recurring_transaction_agreement_file_info
|
||||
.provider_file_id,
|
||||
uncategorized_file: uncategorized_file_info.file_data,
|
||||
uncategorized_file_provider_file_id: uncategorized_file_info.provider_file_id,
|
||||
uncategorized_text: evidence_request.uncategorized_text,
|
||||
cancellation_policy_file_type: cancellation_policy_file_info.file_type,
|
||||
customer_communication_file_type: customer_communication_file_info.file_type,
|
||||
customer_signature_file_type: customer_sifnature_file_info.file_type,
|
||||
receipt_file_type: receipt_file_info.file_type,
|
||||
refund_policy_file_type: refund_policy_file_info.file_type,
|
||||
service_documentation_file_type: service_documentation_file_info.file_type,
|
||||
shipping_documentation_file_type: shipping_documentation_file_info.file_type,
|
||||
invoice_showing_distinct_transactions_file_type:
|
||||
invoice_showing_distinct_transactions_file_info.file_type,
|
||||
recurring_transaction_agreement_file_type: recurring_transaction_agreement_file_info
|
||||
.file_type,
|
||||
uncategorized_file_type: uncategorized_file_info.file_type,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -110,22 +110,22 @@ pub async fn files_retrieve_core(
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::FileNotFound)
|
||||
.attach_printable("Unable to retrieve file_metadata")?;
|
||||
let (received_data, _provider_file_id) =
|
||||
helpers::retrieve_file_and_provider_file_id_from_file_id(
|
||||
&state,
|
||||
Some(req.file_id),
|
||||
&merchant_account,
|
||||
&key_store,
|
||||
api::FileDataRequired::Required,
|
||||
)
|
||||
.await?;
|
||||
let file_info = helpers::retrieve_file_and_provider_file_id_from_file_id(
|
||||
&state,
|
||||
Some(req.file_id),
|
||||
&merchant_account,
|
||||
&key_store,
|
||||
api::FileDataRequired::Required,
|
||||
)
|
||||
.await?;
|
||||
let content_type = file_metadata_object
|
||||
.file_type
|
||||
.parse::<mime::Mime>()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse file content type")?;
|
||||
Ok(ApplicationResponse::FileData((
|
||||
received_data
|
||||
file_info
|
||||
.file_data
|
||||
.ok_or(errors::ApiErrorResponse::FileNotAvailable)
|
||||
.attach_printable("File data not found")?,
|
||||
content_type,
|
||||
|
||||
@ -2,6 +2,7 @@ use actix_multipart::Field;
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::ResultExt;
|
||||
use futures::TryStreamExt;
|
||||
use hyperswitch_domain_models::router_response_types::disputes::FileInfo;
|
||||
|
||||
use crate::{
|
||||
core::{
|
||||
@ -175,9 +176,13 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id(
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
is_connector_file_data_required: api::FileDataRequired,
|
||||
) -> CustomResult<(Option<Vec<u8>>, Option<String>), errors::ApiErrorResponse> {
|
||||
) -> CustomResult<FileInfo, errors::ApiErrorResponse> {
|
||||
match file_id {
|
||||
None => Ok((None, None)),
|
||||
None => Ok(FileInfo {
|
||||
file_data: None,
|
||||
provider_file_id: None,
|
||||
file_type: None,
|
||||
}),
|
||||
Some(file_key) => {
|
||||
let file_metadata_object = state
|
||||
.store
|
||||
@ -194,22 +199,23 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id(
|
||||
.attach_printable("File not available")?,
|
||||
};
|
||||
match provider {
|
||||
diesel_models::enums::FileUploadProvider::Router => Ok((
|
||||
Some(
|
||||
diesel_models::enums::FileUploadProvider::Router => Ok(FileInfo {
|
||||
file_data: Some(
|
||||
state
|
||||
.file_storage_client
|
||||
.retrieve_file(&provider_file_id)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?,
|
||||
),
|
||||
Some(provider_file_id),
|
||||
)),
|
||||
provider_file_id: Some(provider_file_id),
|
||||
file_type: Some(file_metadata_object.file_type),
|
||||
}),
|
||||
_ => {
|
||||
let connector_file_data = match is_connector_file_data_required {
|
||||
api::FileDataRequired::Required => Some(
|
||||
retrieve_file_from_connector(
|
||||
state,
|
||||
file_metadata_object,
|
||||
file_metadata_object.clone(),
|
||||
merchant_account,
|
||||
key_store,
|
||||
)
|
||||
@ -217,7 +223,11 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id(
|
||||
),
|
||||
api::FileDataRequired::NotRequired => None,
|
||||
};
|
||||
Ok((connector_file_data, Some(provider_file_id)))
|
||||
Ok(FileInfo {
|
||||
file_data: connector_file_data,
|
||||
provider_file_id: Some(provider_file_id),
|
||||
file_type: Some(file_metadata_object.file_type),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -623,7 +623,6 @@ impl<const T: u8>
|
||||
default_imp_for_accept_dispute!(
|
||||
connector::Adyenplatform,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
connector::Authorizedotnet,
|
||||
connector::Bamboraapac,
|
||||
@ -739,7 +738,6 @@ impl<const T: u8>
|
||||
default_imp_for_file_upload!(
|
||||
connector::Adyenplatform,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
connector::Authorizedotnet,
|
||||
connector::Bamboraapac,
|
||||
@ -832,7 +830,6 @@ impl<const T: u8>
|
||||
default_imp_for_submit_evidence!(
|
||||
connector::Adyenplatform,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
connector::Authorizedotnet,
|
||||
connector::Bamboraapac,
|
||||
@ -925,7 +922,6 @@ impl<const T: u8>
|
||||
default_imp_for_defend_dispute!(
|
||||
connector::Adyenplatform,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
connector::Authorizedotnet,
|
||||
connector::Bamboraapac,
|
||||
|
||||
Reference in New Issue
Block a user