feat(router): added support for optional defend dispute api call and added evidence submission flow for checkout connector (#979)

This commit is contained in:
Sai Harsha Vardhan
2023-05-03 02:25:53 +05:30
committed by GitHub
parent 0b7bc7bcd2
commit 4728d946e2
13 changed files with 529 additions and 46 deletions

View File

@ -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(

View File

@ -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,
})
}
}

View File

@ -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))
}