mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 20:23:43 +08:00
feat(router): added support for optional defend dispute api call and added evidence submission flow for checkout connector (#979)
This commit is contained in:
committed by
GitHub
parent
0b7bc7bcd2
commit
4728d946e2
@ -122,6 +122,10 @@ pub struct SubmitEvidenceRequest {
|
||||
pub shipping_documentation: Option<String>,
|
||||
/// Tracking number of shipped product
|
||||
pub shipping_tracking_number: Option<String>,
|
||||
/// File Id showing two distinct transactions when customer claims a payment was charged twice
|
||||
pub invoice_showing_distinct_transactions: Option<String>,
|
||||
/// File Id of recurring transaction agreement
|
||||
pub recurring_transaction_agreement: Option<String>,
|
||||
/// Any additional supporting file
|
||||
pub uncategorized_file: Option<String>,
|
||||
/// Any additional evidence statements
|
||||
|
||||
@ -627,7 +627,10 @@ impl Connector {
|
||||
)
|
||||
}
|
||||
pub fn supports_file_storage_module(&self) -> bool {
|
||||
matches!(self, Self::Stripe)
|
||||
matches!(self, Self::Stripe | Self::Checkout)
|
||||
}
|
||||
pub fn requires_defend_dispute(&self) -> bool {
|
||||
matches!(self, Self::Checkout)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -118,6 +118,7 @@ impl api::ConnectorAccessToken for Checkout {}
|
||||
impl api::AcceptDispute for Checkout {}
|
||||
impl api::PaymentToken for Checkout {}
|
||||
impl api::Dispute for Checkout {}
|
||||
impl api::DefendDispute for Checkout {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
@ -766,43 +767,12 @@ impl
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: checkout::ErrorResponse = if res.response.is_empty() {
|
||||
checkout::ErrorResponse {
|
||||
request_id: None,
|
||||
error_type: if res.status_code == 401 {
|
||||
Some("Invalid Api Key".to_owned())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
error_codes: None,
|
||||
}
|
||||
} else {
|
||||
res.response
|
||||
.parse_struct("ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?
|
||||
};
|
||||
|
||||
Ok(types::ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response
|
||||
.error_codes
|
||||
.unwrap_or_else(|| vec![consts::NO_ERROR_CODE.to_string()])
|
||||
.join(" & "),
|
||||
message: response
|
||||
.error_type
|
||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
||||
reason: None,
|
||||
})
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::UploadFile for Checkout {}
|
||||
|
||||
impl ConnectorIntegration<api::Upload, types::UploadFileRequestData, types::UploadFileResponse>
|
||||
for Checkout
|
||||
{
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::FileUpload for Checkout {
|
||||
fn validate_file_upload(
|
||||
@ -832,6 +802,240 @@ impl api::FileUpload for Checkout {
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Upload, types::UploadFileRequestData, types::UploadFileResponse>
|
||||
for Checkout
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RouterData<
|
||||
api::Upload,
|
||||
types::UploadFileRequestData,
|
||||
types::UploadFileResponse,
|
||||
>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.get_auth_header(&req.connector_auth_type)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
"multipart/form-data"
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::UploadFileRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}{}", self.base_url(connectors), "files"))
|
||||
}
|
||||
|
||||
fn get_request_form_data(
|
||||
&self,
|
||||
req: &types::UploadFileRouterData,
|
||||
) -> CustomResult<Option<reqwest::multipart::Form>, errors::ConnectorError> {
|
||||
let checkout_req = transformers::construct_file_upload_request(req.clone())?;
|
||||
Ok(Some(checkout_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::UploadFileRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::UploadFileType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::UploadFileType::get_headers(self, req, connectors)?)
|
||||
.form_data(types::UploadFileType::get_request_form_data(self, req)?)
|
||||
.content_type(services::request::ContentType::FormData)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::UploadFileRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<
|
||||
types::RouterData<api::Upload, types::UploadFileRequestData, types::UploadFileResponse>,
|
||||
errors::ConnectorError,
|
||||
> {
|
||||
let response: checkout::FileUploadResponse = res
|
||||
.response
|
||||
.parse_struct("Checkout FileUploadResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::UploadFileRouterData {
|
||||
response: Ok(types::UploadFileResponse {
|
||||
provider_file_id: response.file_id,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::SubmitEvidence for Checkout {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::Evidence,
|
||||
types::SubmitEvidenceRequestData,
|
||||
types::SubmitEvidenceResponse,
|
||||
> for Checkout
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::SubmitEvidenceType::get_content_type(self).to_string(),
|
||||
)];
|
||||
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> {
|
||||
Ok(format!(
|
||||
"{}disputes/{}/evidence",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_dispute_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let checkout_req = checkout::Evidence::try_from(req)?;
|
||||
let checkout_req_string =
|
||||
utils::Encode::<checkout::Evidence>::encode_to_string_of_json(&checkout_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(checkout_req_string))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::SubmitEvidenceRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Put)
|
||||
.url(&types::SubmitEvidenceType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::SubmitEvidenceType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::SubmitEvidenceType::get_request_body(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::SubmitEvidenceRouterData,
|
||||
_res: types::Response,
|
||||
) -> CustomResult<types::SubmitEvidenceRouterData, errors::ConnectorError> {
|
||||
Ok(types::SubmitEvidenceRouterData {
|
||||
response: Ok(types::SubmitEvidenceResponse {
|
||||
dispute_status: api_models::enums::DisputeStatus::DisputeChallenged,
|
||||
connector_status: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<api::Defend, types::DefendDisputeRequestData, types::DefendDisputeResponse>
|
||||
for Checkout
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::DefendDisputeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::DefendDisputeType::get_content_type(self).to_string(),
|
||||
)];
|
||||
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> {
|
||||
Ok(format!(
|
||||
"{}disputes/{}/evidence",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_dispute_id,
|
||||
))
|
||||
}
|
||||
|
||||
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,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::DefendDisputeRouterData,
|
||||
_res: types::Response,
|
||||
) -> CustomResult<types::DefendDisputeRouterData, errors::ConnectorError> {
|
||||
Ok(types::DefendDisputeRouterData {
|
||||
response: Ok(types::DefendDisputeResponse {
|
||||
dispute_status: api::enums::DisputeStatus::DisputeChallenged,
|
||||
connector_status: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Checkout {
|
||||
fn get_webhook_source_verification_algorithm(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use error_stack::IntoReport;
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@ -775,3 +776,67 @@ impl From<CheckoutTxnType> for api_models::enums::DisputeStage {
|
||||
pub struct CheckoutWebhookObjectResource {
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
pub fn construct_file_upload_request(
|
||||
file_upload_router_data: types::UploadFileRouterData,
|
||||
) -> CustomResult<reqwest::multipart::Form, errors::ConnectorError> {
|
||||
let request = file_upload_router_data.request;
|
||||
let mut multipart = reqwest::multipart::Form::new();
|
||||
multipart = multipart.text("purpose", "dispute_evidence");
|
||||
let file_data = reqwest::multipart::Part::bytes(request.file)
|
||||
.file_name(format!(
|
||||
"{}.{}",
|
||||
request.file_key,
|
||||
request
|
||||
.file_type
|
||||
.to_string()
|
||||
.split('/')
|
||||
.last()
|
||||
.unwrap_or_default()
|
||||
))
|
||||
.mime_str(request.file_type.as_ref())
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)
|
||||
.attach_printable("Failure in constructing file data")?;
|
||||
multipart = multipart.part("file", file_data);
|
||||
Ok(multipart)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FileUploadResponse {
|
||||
#[serde(rename = "id")]
|
||||
pub file_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct Evidence {
|
||||
pub proof_of_delivery_or_service_file: Option<String>,
|
||||
pub invoice_or_receipt_file: Option<String>,
|
||||
pub invoice_showing_distinct_transactions_file: Option<String>,
|
||||
pub customer_communication_file: Option<String>,
|
||||
pub refund_or_cancellation_policy_file: Option<String>,
|
||||
pub recurring_transaction_agreement_file: Option<String>,
|
||||
pub additional_evidence_file: Option<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 submit_evidence_request_data = item.request.clone();
|
||||
Ok(Self {
|
||||
proof_of_delivery_or_service_file: submit_evidence_request_data
|
||||
.shipping_documentation_provider_file_id,
|
||||
invoice_or_receipt_file: submit_evidence_request_data.receipt_provider_file_id,
|
||||
invoice_showing_distinct_transactions_file: submit_evidence_request_data
|
||||
.invoice_showing_distinct_transactions_provider_file_id,
|
||||
customer_communication_file: submit_evidence_request_data
|
||||
.customer_communication_provider_file_id,
|
||||
refund_or_cancellation_policy_file: submit_evidence_request_data
|
||||
.refund_policy_provider_file_id,
|
||||
recurring_transaction_agreement_file: submit_evidence_request_data
|
||||
.recurring_transaction_agreement_provider_file_id,
|
||||
additional_evidence_file: submit_evidence_request_data
|
||||
.uncategorized_file_provider_file_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1128,7 +1128,6 @@ impl
|
||||
let stripe_req = stripe::Evidence::try_from(req)?;
|
||||
let stripe_req_string = utils::Encode::<stripe::Evidence>::url_encode(&stripe_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
print!("Stripe request: {:?}", stripe_req_string);
|
||||
Ok(Some(stripe_req_string))
|
||||
}
|
||||
|
||||
|
||||
@ -15,8 +15,8 @@ use crate::{
|
||||
api::{self, disputes},
|
||||
storage::{self, enums as storage_enums},
|
||||
transformers::{ForeignFrom, ForeignInto},
|
||||
AcceptDisputeRequestData, AcceptDisputeResponse, SubmitEvidenceRequestData,
|
||||
SubmitEvidenceResponse,
|
||||
AcceptDisputeRequestData, AcceptDisputeResponse, DefendDisputeRequestData,
|
||||
DefendDisputeResponse, SubmitEvidenceRequestData, SubmitEvidenceResponse,
|
||||
},
|
||||
};
|
||||
|
||||
@ -245,12 +245,54 @@ pub async fn submit_evidence(
|
||||
status_code: err.status_code,
|
||||
reason: err.reason,
|
||||
})?;
|
||||
//Defend Dispute Optionally if connector expects to defend / submit evidence in a separate api call
|
||||
let (dispute_status, connector_status) =
|
||||
if connector_data.connector_name.requires_defend_dispute() {
|
||||
let connector_integration_defend_dispute: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
api::Defend,
|
||||
DefendDisputeRequestData,
|
||||
DefendDisputeResponse,
|
||||
> = connector_data.connector.get_connector_integration();
|
||||
let defend_dispute_router_data = utils::construct_defend_dispute_router_data(
|
||||
state,
|
||||
&payment_intent,
|
||||
&payment_attempt,
|
||||
&merchant_account,
|
||||
&dispute,
|
||||
)
|
||||
.await?;
|
||||
let defend_response = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration_defend_dispute,
|
||||
&defend_dispute_router_data,
|
||||
payments::CallConnectorAction::Trigger,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed while calling defend dispute connector api")?;
|
||||
let defend_dispute_response = defend_response.response.map_err(|err| {
|
||||
errors::ApiErrorResponse::ExternalConnectorError {
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
connector: dispute.connector.clone(),
|
||||
status_code: err.status_code,
|
||||
reason: err.reason,
|
||||
}
|
||||
})?;
|
||||
(
|
||||
defend_dispute_response.dispute_status,
|
||||
defend_dispute_response.connector_status,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
submit_evidence_response.dispute_status,
|
||||
submit_evidence_response.connector_status,
|
||||
)
|
||||
};
|
||||
let update_dispute = storage_models::dispute::DisputeUpdate::StatusUpdate {
|
||||
dispute_status: submit_evidence_response
|
||||
.dispute_status
|
||||
.clone()
|
||||
.foreign_into(),
|
||||
connector_status: submit_evidence_response.connector_status.clone(),
|
||||
dispute_status: dispute_status.foreign_into(),
|
||||
connector_status,
|
||||
};
|
||||
let updated_dispute = db
|
||||
.update_dispute(dispute.clone(), update_dispute)
|
||||
|
||||
@ -60,6 +60,22 @@ pub async fn get_evidence_request_data(
|
||||
merchant_account,
|
||||
)
|
||||
.await?;
|
||||
let (
|
||||
invoice_showing_distinct_transactions,
|
||||
invoice_showing_distinct_transactions_provider_file_id,
|
||||
) = retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.invoice_showing_distinct_transactions,
|
||||
merchant_account,
|
||||
)
|
||||
.await?;
|
||||
let (recurring_transaction_agreement, recurring_transaction_agreement_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
evidence_request.recurring_transaction_agreement,
|
||||
merchant_account,
|
||||
)
|
||||
.await?;
|
||||
let (uncategorized_file, uncategorized_file_provider_file_id) =
|
||||
retrieve_file_and_provider_file_id_from_file_id(
|
||||
state,
|
||||
@ -99,6 +115,10 @@ pub async fn get_evidence_request_data(
|
||||
shipping_documentation,
|
||||
shipping_documentation_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,
|
||||
uncategorized_text: evidence_request.uncategorized_text,
|
||||
|
||||
@ -309,7 +309,6 @@ default_imp_for_submit_evidence!(
|
||||
connector::Bambora,
|
||||
connector::Bluesnap,
|
||||
connector::Braintree,
|
||||
connector::Checkout,
|
||||
connector::Cybersource,
|
||||
connector::Coinbase,
|
||||
connector::Dlocal,
|
||||
@ -332,3 +331,50 @@ default_imp_for_submit_evidence!(
|
||||
connector::Worldpay,
|
||||
connector::Zen
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_defend_dispute{
|
||||
($($path:ident::$connector:ident),*)=> {
|
||||
$(
|
||||
impl api::DefendDispute for $path::$connector {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Defend,
|
||||
types::DefendDisputeRequestData,
|
||||
types::DefendDisputeResponse,
|
||||
> for $path::$connector
|
||||
{}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
default_imp_for_defend_dispute!(
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
connector::Authorizedotnet,
|
||||
connector::Bambora,
|
||||
connector::Bluesnap,
|
||||
connector::Braintree,
|
||||
connector::Cybersource,
|
||||
connector::Coinbase,
|
||||
connector::Dlocal,
|
||||
connector::Fiserv,
|
||||
connector::Forte,
|
||||
connector::Globalpay,
|
||||
connector::Klarna,
|
||||
connector::Mollie,
|
||||
connector::Multisafepay,
|
||||
connector::Nexinets,
|
||||
connector::Nuvei,
|
||||
connector::Payeezy,
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Rapyd,
|
||||
connector::Stripe,
|
||||
connector::Shift4,
|
||||
connector::Trustpay,
|
||||
connector::Opennode,
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ use crate::{
|
||||
types::{
|
||||
self,
|
||||
storage::{self, enums},
|
||||
ErrorResponse,
|
||||
},
|
||||
utils::{generate_id, OptionExt, ValueExt},
|
||||
};
|
||||
@ -400,3 +401,62 @@ pub async fn construct_upload_file_router_data<'a>(
|
||||
};
|
||||
Ok(router_data)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn construct_defend_dispute_router_data<'a>(
|
||||
state: &'a AppState,
|
||||
payment_intent: &'a storage::PaymentIntent,
|
||||
payment_attempt: &storage::PaymentAttempt,
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
dispute: &storage::Dispute,
|
||||
) -> RouterResult<types::DefendDisputeRouterData> {
|
||||
let db = &*state.store;
|
||||
let connector_id = &dispute.connector;
|
||||
let connector_label = helpers::get_connector_label(
|
||||
payment_intent.business_country,
|
||||
&payment_intent.business_label,
|
||||
payment_attempt.business_sub_label.as_ref(),
|
||||
connector_id,
|
||||
);
|
||||
let merchant_connector_account = helpers::get_merchant_connector_account(
|
||||
db,
|
||||
merchant_account.merchant_id.as_str(),
|
||||
&connector_label,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let auth_type: types::ConnectorAuthType = merchant_connector_account
|
||||
.get_connector_account_details()
|
||||
.parse_value("ConnectorAuthType")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let payment_method = payment_attempt
|
||||
.payment_method
|
||||
.get_required_value("payment_method_type")?;
|
||||
let router_data = types::RouterData {
|
||||
flow: PhantomData,
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
connector: connector_id.to_string(),
|
||||
payment_id: payment_attempt.payment_id.clone(),
|
||||
attempt_id: payment_attempt.attempt_id.clone(),
|
||||
status: payment_attempt.status,
|
||||
payment_method,
|
||||
connector_auth_type: auth_type,
|
||||
description: None,
|
||||
return_url: payment_intent.return_url.clone(),
|
||||
payment_method_id: payment_attempt.payment_method_id.clone(),
|
||||
address: PaymentAddress::default(),
|
||||
auth_type: payment_attempt.authentication_type.unwrap_or_default(),
|
||||
connector_meta_data: merchant_connector_account.get_metadata(),
|
||||
amount_captured: payment_intent.amount_captured,
|
||||
request: types::DefendDisputeRequestData {
|
||||
dispute_id: dispute.dispute_id.clone(),
|
||||
connector_dispute_id: dispute.connector_dispute_id.clone(),
|
||||
},
|
||||
response: Err(ErrorResponse::get_not_implemented()),
|
||||
access_token: None,
|
||||
session_token: None,
|
||||
reference_id: None,
|
||||
payment_method_token: None,
|
||||
};
|
||||
Ok(router_data)
|
||||
}
|
||||
|
||||
@ -120,6 +120,12 @@ pub type SubmitEvidenceType = dyn services::ConnectorIntegration<
|
||||
pub type UploadFileType =
|
||||
dyn services::ConnectorIntegration<api::Upload, UploadFileRequestData, UploadFileResponse>;
|
||||
|
||||
pub type DefendDisputeType = dyn services::ConnectorIntegration<
|
||||
api::Defend,
|
||||
DefendDisputeRequestData,
|
||||
DefendDisputeResponse,
|
||||
>;
|
||||
|
||||
pub type VerifyRouterData = RouterData<api::Verify, VerifyRequestData, PaymentsResponseData>;
|
||||
|
||||
pub type AcceptDisputeRouterData =
|
||||
@ -130,6 +136,9 @@ pub type SubmitEvidenceRouterData =
|
||||
|
||||
pub type UploadFileRouterData = RouterData<api::Upload, UploadFileRequestData, UploadFileResponse>;
|
||||
|
||||
pub type DefendDisputeRouterData =
|
||||
RouterData<api::Defend, DefendDisputeRequestData, DefendDisputeResponse>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RouterData<Flow, Request, Response> {
|
||||
pub flow: PhantomData<Flow>,
|
||||
@ -425,6 +434,10 @@ pub struct SubmitEvidenceRequestData {
|
||||
pub shipping_documentation: Option<Vec<u8>>,
|
||||
pub shipping_documentation_provider_file_id: Option<String>,
|
||||
pub shipping_tracking_number: Option<String>,
|
||||
pub invoice_showing_distinct_transactions: Option<Vec<u8>>,
|
||||
pub invoice_showing_distinct_transactions_provider_file_id: Option<String>,
|
||||
pub recurring_transaction_agreement: Option<Vec<u8>>,
|
||||
pub recurring_transaction_agreement_provider_file_id: Option<String>,
|
||||
pub uncategorized_file: Option<Vec<u8>>,
|
||||
pub uncategorized_file_provider_file_id: Option<String>,
|
||||
pub uncategorized_text: Option<String>,
|
||||
@ -436,6 +449,18 @@ pub struct SubmitEvidenceResponse {
|
||||
pub connector_status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct DefendDisputeRequestData {
|
||||
pub dispute_id: String,
|
||||
pub connector_dispute_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct DefendDisputeResponse {
|
||||
pub dispute_status: api_models::enums::DisputeStatus,
|
||||
pub connector_status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UploadFileRequestData {
|
||||
pub file_key: String,
|
||||
|
||||
@ -45,4 +45,16 @@ pub trait SubmitEvidence:
|
||||
{
|
||||
}
|
||||
|
||||
pub trait Dispute: super::ConnectorCommon + AcceptDispute + SubmitEvidence {}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Defend;
|
||||
|
||||
pub trait DefendDispute:
|
||||
services::ConnectorIntegration<
|
||||
Defend,
|
||||
types::DefendDisputeRequestData,
|
||||
types::DefendDisputeResponse,
|
||||
>
|
||||
{
|
||||
}
|
||||
|
||||
pub trait Dispute: super::ConnectorCommon + AcceptDispute + SubmitEvidence + DefendDispute {}
|
||||
|
||||
@ -12,13 +12,15 @@ pub struct FileId {
|
||||
pub enum FileUploadProvider {
|
||||
Router,
|
||||
Stripe,
|
||||
Checkout,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::Connector> for FileUploadProvider {
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
fn try_from(item: &types::Connector) -> Result<Self, Self::Error> {
|
||||
match item {
|
||||
&types::Connector::Stripe => Ok(Self::Stripe),
|
||||
match *item {
|
||||
types::Connector::Stripe => Ok(Self::Stripe),
|
||||
types::Connector::Checkout => Ok(Self::Checkout),
|
||||
_ => Err(errors::ApiErrorResponse::NotSupported {
|
||||
message: "Connector not supported as file provider".to_owned(),
|
||||
}
|
||||
|
||||
@ -817,4 +817,5 @@ pub enum FileUploadProvider {
|
||||
#[default]
|
||||
Router,
|
||||
Stripe,
|
||||
Checkout,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user