mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(connector): add authorize, capture, void, refund, psync, rsync support for Dlocal connector (#650)
Co-authored-by: Venkatesh <inventvenkat@gmail.com>
This commit is contained in:
@ -44,7 +44,7 @@ locker_decryption_key2 = ""
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree","applepay"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay", "fiserv", "payu", "worldline"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay", "fiserv", "payu", "worldline", "dlocal"]
|
||||
|
||||
[refund]
|
||||
max_attempts = 10
|
||||
@ -104,6 +104,9 @@ base_url = "https://apis.sandbox.globalpay.com/ucp/"
|
||||
[connectors.worldline]
|
||||
base_url = "https://eu.sandbox.api-ingenico.com/"
|
||||
|
||||
[connectors.dlocal]
|
||||
base_url = "https://sandbox.dlocal.com/"
|
||||
|
||||
[scheduler]
|
||||
stream = "SCHEDULER_STREAM"
|
||||
|
||||
|
||||
@ -154,6 +154,9 @@ base_url = "https://try.access.worldpay.com/"
|
||||
base_url = "https://apis.sandbox.globalpay.com/ucp/"
|
||||
|
||||
# This data is used to call respective connectors for wallets and cards
|
||||
[connectors.dlocal]
|
||||
base_url = "https://sandbox.dlocal.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
cards = [
|
||||
|
||||
@ -105,9 +105,12 @@ base_url = "https://try.access.worldpay.com/"
|
||||
[connectors.globalpay]
|
||||
base_url = "https://apis.sandbox.globalpay.com/ucp/"
|
||||
|
||||
[connectors.dlocal]
|
||||
base_url = "https://sandbox.dlocal.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay", "globalpay", "fiserv"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay", "globalpay", "fiserv", "dlocal"]
|
||||
|
||||
|
||||
[scheduler]
|
||||
|
||||
@ -579,6 +579,7 @@ pub enum Connector {
|
||||
Cybersource,
|
||||
#[default]
|
||||
Dummy,
|
||||
Dlocal,
|
||||
Fiserv,
|
||||
Globalpay,
|
||||
Klarna,
|
||||
@ -617,6 +618,7 @@ pub enum RoutableConnectors {
|
||||
Braintree,
|
||||
Checkout,
|
||||
Cybersource,
|
||||
Dlocal,
|
||||
Fiserv,
|
||||
Globalpay,
|
||||
Klarna,
|
||||
|
||||
@ -14,7 +14,12 @@ pub mod validation;
|
||||
|
||||
/// Date-time utilities.
|
||||
pub mod date_time {
|
||||
use time::{Instant, OffsetDateTime, PrimitiveDateTime};
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use time::{
|
||||
format_description::well_known::iso8601::{Config, EncodedConfig, Iso8601, TimePrecision},
|
||||
Instant, OffsetDateTime, PrimitiveDateTime,
|
||||
};
|
||||
/// Struct to represent milliseconds in time sensitive data fields
|
||||
#[derive(Debug)]
|
||||
pub struct Milliseconds(i32);
|
||||
@ -43,6 +48,16 @@ pub mod date_time {
|
||||
let result = block().await;
|
||||
(result, start.elapsed().as_seconds_f64() * 1000f64)
|
||||
}
|
||||
|
||||
/// Return the current date and time in UTC with the format [year]-[month]-[day]T[hour]:[minute]:[second].mmmZ Eg: 2023-02-15T13:33:18.898Z
|
||||
pub fn date_as_yyyymmddthhmmssmmmz() -> Result<String, time::error::Format> {
|
||||
const ISO_CONFIG: EncodedConfig = Config::DEFAULT
|
||||
.set_time_precision(TimePrecision::Second {
|
||||
decimal_digits: NonZeroU8::new(3),
|
||||
})
|
||||
.encode();
|
||||
now().assume_utc().format(&Iso8601::<ISO_CONFIG>)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a nanoid with the given prefix and length
|
||||
|
||||
@ -142,6 +142,7 @@ pub struct Connectors {
|
||||
pub braintree: ConnectorParams,
|
||||
pub checkout: ConnectorParams,
|
||||
pub cybersource: ConnectorParams,
|
||||
pub dlocal: ConnectorParams,
|
||||
pub fiserv: ConnectorParams,
|
||||
pub globalpay: ConnectorParams,
|
||||
pub klarna: ConnectorParams,
|
||||
|
||||
@ -16,9 +16,11 @@ pub mod utils;
|
||||
pub mod worldline;
|
||||
pub mod worldpay;
|
||||
|
||||
pub mod dlocal;
|
||||
|
||||
pub use self::{
|
||||
aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
|
||||
braintree::Braintree, checkout::Checkout, cybersource::Cybersource, fiserv::Fiserv,
|
||||
globalpay::Globalpay, klarna::Klarna, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe,
|
||||
worldline::Worldline, worldpay::Worldpay,
|
||||
braintree::Braintree, checkout::Checkout, cybersource::Cybersource, dlocal::Dlocal,
|
||||
fiserv::Fiserv, globalpay::Globalpay, klarna::Klarna, payu::Payu, rapyd::Rapyd, shift4::Shift4,
|
||||
stripe::Stripe, worldline::Worldline, worldpay::Worldpay,
|
||||
};
|
||||
|
||||
597
crates/router/src/connector/dlocal.rs
Normal file
597
crates/router/src/connector/dlocal.rs
Normal file
@ -0,0 +1,597 @@
|
||||
mod transformers;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use common_utils::{
|
||||
crypto::{self, SignMessage},
|
||||
date_time,
|
||||
};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use hex::encode;
|
||||
use transformers as dlocal;
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers, logger,
|
||||
services::{self, ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
ErrorResponse, Response,
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dlocal;
|
||||
|
||||
impl api::Payment for Dlocal {}
|
||||
impl api::PaymentSession for Dlocal {}
|
||||
impl api::ConnectorAccessToken for Dlocal {}
|
||||
impl api::PreVerify for Dlocal {}
|
||||
impl api::PaymentAuthorize for Dlocal {}
|
||||
impl api::PaymentSync for Dlocal {}
|
||||
impl api::PaymentCapture for Dlocal {}
|
||||
impl api::PaymentVoid for Dlocal {}
|
||||
impl api::Refund for Dlocal {}
|
||||
impl api::RefundExecute for Dlocal {}
|
||||
impl api::RefundSync for Dlocal {}
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Dlocal
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
{
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let dlocal_req = match self.get_request_body(req)? {
|
||||
Some(val) => val,
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let date = date_time::date_as_yyyymmddthhmmssmmmz()
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
let auth = dlocal::DlocalAuthType::try_from(&req.connector_auth_type)?;
|
||||
let sign_req: String = format!("{}{}{}", auth.x_login, date, dlocal_req);
|
||||
let authz = crypto::HmacSha256::sign_message(
|
||||
&crypto::HmacSha256,
|
||||
auth.secret.as_bytes(),
|
||||
sign_req.as_bytes(),
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)
|
||||
.attach_printable("Failed to sign the message")?;
|
||||
let auth_string: String = format!("V2-HMAC-SHA256, Signature: {}", encode(authz));
|
||||
let headers = vec![
|
||||
(headers::AUTHORIZATION.to_string(), auth_string),
|
||||
(headers::X_LOGIN.to_string(), auth.x_login),
|
||||
(headers::X_TRANS_KEY.to_string(), auth.x_trans_key),
|
||||
(headers::X_VERSION.to_string(), "2.1".to_string()),
|
||||
(headers::X_DATE.to_string(), date),
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
Self.get_content_type().to_string(),
|
||||
),
|
||||
];
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Dlocal {
|
||||
fn id(&self) -> &'static str {
|
||||
"dlocal"
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||
connectors.dlocal.base_url.as_ref()
|
||||
}
|
||||
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: dlocal::DlocalErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.code.to_string(),
|
||||
message: response.message,
|
||||
reason: response.param,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
//TODO: implement sessions flow
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
for Dlocal
|
||||
{
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}secure_payments", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let dlocal_req = utils::Encode::<dlocal::DlocalPaymentsRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(dlocal_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsAuthorizeType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.headers(types::PaymentsAuthorizeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsAuthorizeRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_payments_authorize_response=?res);
|
||||
let response: dlocal::DlocalPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal PaymentsAuthorizeResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
}
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let sync_data = dlocal::DlocalPaymentsSyncRequest::try_from(req)?;
|
||||
Ok(format!(
|
||||
"{}payments/{}/status",
|
||||
self.base_url(connectors),
|
||||
sync_data.authz_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Get)
|
||||
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_payment_sync_response=?res);
|
||||
let response: dlocal::DlocalPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal PaymentsSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}payments", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let dlocal_req =
|
||||
utils::Encode::<dlocal::DlocalPaymentsCaptureRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(dlocal_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsCaptureType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCaptureRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_payments_capture_response=?res);
|
||||
let response: dlocal::DlocalPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal PaymentsCaptureResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
}
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Dlocal
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let cancel_data = dlocal::DlocalPaymentsCancelRequest::try_from(req)?;
|
||||
Ok(format!(
|
||||
"{}payments/{}/cancel",
|
||||
self.base_url(connectors),
|
||||
cancel_data.cancel_id
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCancelRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_payments_cancel_response=?res);
|
||||
let response: dlocal::DlocalPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal PaymentsCancelResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
}
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Dlocal {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}refunds", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let dlocal_req = utils::Encode::<dlocal::RefundRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(dlocal_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundExecuteType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::RefundExecuteType::get_request_body(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundsRouterData<api::Execute>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_refund_response=?res);
|
||||
let response: dlocal::RefundResponse =
|
||||
res.response
|
||||
.parse_struct("Dlocal RefundResponse")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
}
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Dlocal {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let sync_data = dlocal::DlocalRefundsSyncRequest::try_from(req)?;
|
||||
Ok(format!(
|
||||
"{}refunds/{}/status",
|
||||
self.base_url(connectors),
|
||||
sync_data.refund_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Get)
|
||||
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||
logger::debug!(dlocal_refund_sync_response=?res);
|
||||
let response: dlocal::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("Dlocal RefundSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
}
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Dlocal {
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
}
|
||||
|
||||
impl services::ConnectorRedirectResponse for Dlocal {
|
||||
fn get_flow_type(
|
||||
&self,
|
||||
_query_params: &str,
|
||||
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
|
||||
Ok(payments::CallConnectorAction::Trigger)
|
||||
}
|
||||
}
|
||||
513
crates/router/src/connector/dlocal/transformers.rs
Normal file
513
crates/router/src/connector/dlocal/transformers.rs
Normal file
@ -0,0 +1,513 @@
|
||||
use api_models::payments::AddressDetails;
|
||||
use common_utils::pii::{self, Email};
|
||||
use error_stack::ResultExt;
|
||||
use masking::{PeekInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
connector::utils::{AddressDetailsData, PaymentsRequestData},
|
||||
core::errors,
|
||||
services,
|
||||
types::{self, api, storage::enums},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct Payer {
|
||||
pub name: Option<Secret<String>>,
|
||||
pub email: Option<Secret<String, Email>>,
|
||||
pub document: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Card {
|
||||
pub holder_name: Secret<String>,
|
||||
pub number: Secret<String, pii::CardNumber>,
|
||||
pub cvv: Secret<String>,
|
||||
pub expiration_month: Secret<String>,
|
||||
pub expiration_year: Secret<String>,
|
||||
pub capture: String,
|
||||
pub installments_id: Option<String>,
|
||||
pub installments: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct ThreeDSecureReqData {
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum PaymentMethodId {
|
||||
#[default]
|
||||
Card,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum PaymentMethodFlow {
|
||||
#[default]
|
||||
Direct,
|
||||
ReDirect,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct DlocalPaymentsRequest {
|
||||
pub amount: i64,
|
||||
pub currency: enums::Currency,
|
||||
pub country: String,
|
||||
pub payment_method_id: PaymentMethodId,
|
||||
pub payment_method_flow: PaymentMethodFlow,
|
||||
pub payer: Payer,
|
||||
pub card: Option<Card>,
|
||||
pub order_id: String,
|
||||
pub three_dsecure: Option<ThreeDSecureReqData>,
|
||||
pub callback_url: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for DlocalPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
let email = item.request.email.clone();
|
||||
let address = item.get_billing_address()?;
|
||||
let country = address.get_country()?;
|
||||
let name = get_payer_name(address);
|
||||
match item.request.payment_method_data {
|
||||
api::PaymentMethod::Card(ref ccard) => {
|
||||
let should_capture = matches!(
|
||||
item.request.capture_method,
|
||||
Some(enums::CaptureMethod::Automatic)
|
||||
);
|
||||
let payment_request = Self {
|
||||
amount: item.request.amount,
|
||||
currency: item.request.currency,
|
||||
payment_method_id: PaymentMethodId::Card,
|
||||
payment_method_flow: PaymentMethodFlow::Direct,
|
||||
country: country.to_string(),
|
||||
payer: Payer {
|
||||
name,
|
||||
email,
|
||||
// [#589]: Allow securely collecting PII from customer in payments request
|
||||
document: get_doc_from_currency(country.to_string()),
|
||||
},
|
||||
card: Some(Card {
|
||||
holder_name: ccard.card_holder_name.clone(),
|
||||
number: ccard.card_number.clone(),
|
||||
cvv: ccard.card_cvc.clone(),
|
||||
expiration_month: ccard.card_exp_month.clone(),
|
||||
expiration_year: ccard.card_exp_year.clone(),
|
||||
capture: should_capture.to_string(),
|
||||
installments_id: item
|
||||
.request
|
||||
.mandate_id
|
||||
.as_ref()
|
||||
.map(|ids| ids.mandate_id.clone()),
|
||||
// [#595[FEATURE] Pass Mandate history information in payment flows/request]
|
||||
installments: item.request.mandate_id.clone().map(|_| "1".to_string()),
|
||||
}),
|
||||
order_id: item.payment_id.clone(),
|
||||
three_dsecure: match item.auth_type {
|
||||
storage_models::enums::AuthenticationType::ThreeDs => {
|
||||
Some(ThreeDSecureReqData { force: true })
|
||||
}
|
||||
storage_models::enums::AuthenticationType::NoThreeDs => None,
|
||||
},
|
||||
callback_url: item.return_url.clone(),
|
||||
};
|
||||
Ok(payment_request)
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_payer_name(address: &AddressDetails) -> Option<Secret<String>> {
|
||||
let first_name = address
|
||||
.first_name
|
||||
.clone()
|
||||
.map_or("".to_string(), |first_name| first_name.peek().to_string());
|
||||
let last_name = address
|
||||
.last_name
|
||||
.clone()
|
||||
.map_or("".to_string(), |last_name| last_name.peek().to_string());
|
||||
let name: String = format!("{} {}", first_name, last_name).trim().to_string();
|
||||
if !name.is_empty() {
|
||||
Some(Secret::new(name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DlocalPaymentsSyncRequest {
|
||||
pub authz_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSyncRouterData> for DlocalPaymentsSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
authz_id: (item
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DlocalPaymentsCancelRequest {
|
||||
pub cancel_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCancelRouterData> for DlocalPaymentsCancelRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
cancel_id: item.request.connector_transaction_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct DlocalPaymentsCaptureRequest {
|
||||
pub authorization_id: String,
|
||||
pub amount: i64,
|
||||
pub currency: String,
|
||||
pub order_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCaptureRouterData> for DlocalPaymentsCaptureRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
|
||||
let amount_to_capture = match item.request.amount_to_capture {
|
||||
Some(val) => val,
|
||||
None => item.request.amount,
|
||||
};
|
||||
Ok(Self {
|
||||
authorization_id: item.request.connector_transaction_id.clone(),
|
||||
amount: amount_to_capture,
|
||||
currency: item.request.currency.to_string(),
|
||||
order_id: item.payment_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Auth Struct
|
||||
pub struct DlocalAuthType {
|
||||
pub(super) x_login: String,
|
||||
pub(super) x_trans_key: String,
|
||||
pub(super) secret: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::ConnectorAuthType> for DlocalAuthType {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
if let types::ConnectorAuthType::SignatureKey {
|
||||
api_key,
|
||||
key1,
|
||||
api_secret,
|
||||
} = auth_type
|
||||
{
|
||||
Ok(Self {
|
||||
x_login: api_key.to_string(),
|
||||
x_trans_key: key1.to_string(),
|
||||
secret: api_secret.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(errors::ConnectorError::FailedToObtainAuthType.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Eq, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum DlocalPaymentStatus {
|
||||
Authorized,
|
||||
Paid,
|
||||
Verified,
|
||||
Cancelled,
|
||||
#[default]
|
||||
Pending,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
impl From<DlocalPaymentStatus> for enums::AttemptStatus {
|
||||
fn from(item: DlocalPaymentStatus) -> Self {
|
||||
match item {
|
||||
DlocalPaymentStatus::Authorized => Self::Authorized,
|
||||
DlocalPaymentStatus::Verified => Self::Authorized,
|
||||
DlocalPaymentStatus::Paid => Self::Charged,
|
||||
DlocalPaymentStatus::Pending => Self::AuthenticationPending,
|
||||
DlocalPaymentStatus::Cancelled => Self::Voided,
|
||||
DlocalPaymentStatus::Rejected => Self::AuthenticationFailed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ThreeDSecureResData {
|
||||
pub redirect_url: Option<Url>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DlocalPaymentsResponse {
|
||||
status: DlocalPaymentStatus,
|
||||
id: String,
|
||||
three_dsecure: Option<ThreeDSecureResData>,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, DlocalPaymentsResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, DlocalPaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data = item
|
||||
.response
|
||||
.three_dsecure
|
||||
.and_then(|three_secure_data| three_secure_data.redirect_url)
|
||||
.map(|redirect_url| {
|
||||
services::RedirectForm::from((redirect_url, services::Method::Get))
|
||||
});
|
||||
|
||||
let response = types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
};
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(response),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DlocalPaymentsSyncResponse {
|
||||
status: DlocalPaymentStatus,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<
|
||||
types::ResponseRouterData<F, DlocalPaymentsSyncResponse, T, types::PaymentsResponseData>,
|
||||
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
DlocalPaymentsSyncResponse,
|
||||
T,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DlocalPaymentsCaptureResponse {
|
||||
status: DlocalPaymentStatus,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<
|
||||
types::ResponseRouterData<F, DlocalPaymentsCaptureResponse, T, types::PaymentsResponseData>,
|
||||
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
DlocalPaymentsCaptureResponse,
|
||||
T,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DlocalPaymentsCancelResponse {
|
||||
status: DlocalPaymentStatus,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<
|
||||
types::ResponseRouterData<F, DlocalPaymentsCancelResponse, T, types::PaymentsResponseData>,
|
||||
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<
|
||||
F,
|
||||
DlocalPaymentsCancelResponse,
|
||||
T,
|
||||
types::PaymentsResponseData,
|
||||
>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// REFUND :
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct RefundRequest {
|
||||
pub amount: String,
|
||||
pub payment_id: String,
|
||||
pub currency: enums::Currency,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for RefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
let amount_to_refund = item.request.refund_amount.to_string();
|
||||
Ok(Self {
|
||||
amount: amount_to_refund,
|
||||
payment_id: item.request.connector_transaction_id.clone(),
|
||||
currency: item.request.currency,
|
||||
id: item.request.refund_id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum RefundStatus {
|
||||
Success,
|
||||
#[default]
|
||||
Pending,
|
||||
Rejected,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl From<RefundStatus> for enums::RefundStatus {
|
||||
fn from(item: RefundStatus) -> Self {
|
||||
match item {
|
||||
RefundStatus::Success => Self::Success,
|
||||
RefundStatus::Pending => Self::Pending,
|
||||
RefundStatus::Rejected => Self::ManualReview,
|
||||
RefundStatus::Cancelled => Self::Failure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RefundResponse {
|
||||
pub id: String,
|
||||
pub status: RefundStatus,
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
|
||||
for types::RefundsRouterData<api::Execute>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let refund_status = enums::RefundStatus::from(item.response.status);
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: item.response.id,
|
||||
refund_status,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DlocalRefundsSyncRequest {
|
||||
pub refund_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::RefundSyncRouterData> for DlocalRefundsSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundSyncRouterData) -> Result<Self, Self::Error> {
|
||||
let refund_id = match item.request.connector_refund_id.clone() {
|
||||
Some(val) => val,
|
||||
None => item.request.refund_id.clone(),
|
||||
};
|
||||
Ok(Self {
|
||||
refund_id: (refund_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
|
||||
for types::RefundsRouterData<api::RSync>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let refund_status = enums::RefundStatus::from(item.response.status);
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: item.response.id,
|
||||
refund_status,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DlocalErrorResponse {
|
||||
pub code: i32,
|
||||
pub message: String,
|
||||
pub param: Option<String>,
|
||||
}
|
||||
|
||||
fn get_doc_from_currency(country: String) -> Secret<String> {
|
||||
let doc = match country.as_str() {
|
||||
"BR" => "91483309223",
|
||||
"ZA" => "2001014800086",
|
||||
"BD" | "GT" | "HN" | "PK" | "SN" | "TH" => "1234567890001",
|
||||
"CR" | "SV" | "VN" => "123456789",
|
||||
"DO" | "NG" => "12345678901",
|
||||
"EG" => "12345678901112",
|
||||
"GH" | "ID" | "RW" | "UG" => "1234567890111123",
|
||||
"IN" => "NHSTP6374G",
|
||||
"CI" => "CA124356789",
|
||||
"JP" | "MY" | "PH" => "123456789012",
|
||||
"NI" => "1234567890111A",
|
||||
"TZ" => "12345678912345678900",
|
||||
_ => "12345678",
|
||||
};
|
||||
Secret::new(doc.to_string())
|
||||
}
|
||||
@ -38,6 +38,7 @@ pub trait PaymentsRequestData {
|
||||
fn get_billing(&self) -> Result<&api::Address, Error>;
|
||||
fn get_billing_country(&self) -> Result<String, Error>;
|
||||
fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error>;
|
||||
fn get_billing_address(&self) -> Result<&api::AddressDetails, Error>;
|
||||
fn get_card(&self) -> Result<api::Card, Error>;
|
||||
fn get_return_url(&self) -> Result<String, Error>;
|
||||
}
|
||||
@ -79,6 +80,13 @@ impl PaymentsRequestData for types::PaymentsAuthorizeRouterData {
|
||||
.and_then(|a| a.phone.as_ref())
|
||||
.ok_or_else(missing_field_err("billing.phone"))
|
||||
}
|
||||
fn get_billing_address(&self) -> Result<&api::AddressDetails, Error> {
|
||||
self.address
|
||||
.billing
|
||||
.as_ref()
|
||||
.and_then(|a| a.address.as_ref())
|
||||
.ok_or_else(missing_field_err("billing.address"))
|
||||
}
|
||||
fn get_billing(&self) -> Result<&api::Address, Error> {
|
||||
self.address
|
||||
.billing
|
||||
|
||||
@ -50,6 +50,10 @@ pub mod headers {
|
||||
pub const X_API_VERSION: &str = "X-ApiVersion";
|
||||
pub const DATE: &str = "Date";
|
||||
pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
|
||||
pub const X_LOGIN: &str = "X-Login";
|
||||
pub const X_TRANS_KEY: &str = "X-Trans-Key";
|
||||
pub const X_VERSION: &str = "X-Version";
|
||||
pub const X_DATE: &str = "X-Date";
|
||||
}
|
||||
|
||||
pub mod pii {
|
||||
|
||||
@ -167,6 +167,7 @@ impl ConnectorData {
|
||||
"braintree" => Ok(Box::new(&connector::Braintree)),
|
||||
"checkout" => Ok(Box::new(&connector::Checkout)),
|
||||
"cybersource" => Ok(Box::new(&connector::Cybersource)),
|
||||
"dlocal" => Ok(Box::new(&connector::Dlocal)),
|
||||
"fiserv" => Ok(Box::new(&connector::Fiserv)),
|
||||
"globalpay" => Ok(Box::new(&connector::Globalpay)),
|
||||
"klarna" => Ok(Box::new(&connector::Klarna)),
|
||||
|
||||
@ -3,6 +3,7 @@ use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub(crate) struct ConnectorAuthentication {
|
||||
pub dlocal: Option<SignatureKey>,
|
||||
pub aci: Option<BodyKey>,
|
||||
pub adyen: Option<BodyKey>,
|
||||
pub authorizedotnet: Option<BodyKey>,
|
||||
|
||||
465
crates/router/tests/connectors/dlocal.rs
Normal file
465
crates/router/tests/connectors/dlocal.rs
Normal file
@ -0,0 +1,465 @@
|
||||
use api_models::payments::Address;
|
||||
use masking::Secret;
|
||||
use router::types::{self, api, storage::enums, PaymentAddress};
|
||||
|
||||
use crate::{
|
||||
connector_auth,
|
||||
utils::{self, ConnectorActions, PaymentInfo},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DlocalTest;
|
||||
impl ConnectorActions for DlocalTest {}
|
||||
impl utils::Connector for DlocalTest {
|
||||
fn get_data(&self) -> types::api::ConnectorData {
|
||||
use router::connector::Dlocal;
|
||||
types::api::ConnectorData {
|
||||
connector: Box::new(&Dlocal),
|
||||
connector_name: types::Connector::Dlocal,
|
||||
get_token: types::api::GetToken::Connector,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||
types::ConnectorAuthType::from(
|
||||
connector_auth::ConnectorAuthentication::new()
|
||||
.dlocal
|
||||
.expect("Missing connector authentication configuration"),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"dlocal".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
static CONNECTOR: DlocalTest = DlocalTest {};
|
||||
|
||||
// Cards Positive Tests
|
||||
// Creates a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_only_authorize_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_payment(None, Some(get_payment_info()))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
}
|
||||
|
||||
// Captures a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(None, None, Some(get_payment_info()))
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Partially captures a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
None,
|
||||
Some(types::PaymentsCaptureData {
|
||||
amount_to_capture: Some(50),
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Synchronizes a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_authorized_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(None, Some(get_payment_info()))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Authorized,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
encoded_data: None,
|
||||
capture_method: None,
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.expect("PSync response");
|
||||
println!("{}", response.status);
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized,);
|
||||
}
|
||||
|
||||
// Voids a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_void_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_void_payment(
|
||||
None,
|
||||
Some(types::PaymentsCancelData {
|
||||
connector_transaction_id: String::from(""),
|
||||
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.expect("Void payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Voided);
|
||||
}
|
||||
|
||||
// Refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_manually_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(None, None, None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Partially refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_manually_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
None,
|
||||
None,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Synchronizes a refund using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_manually_captured_refund() {
|
||||
let refund_response = CONNECTOR
|
||||
.capture_payment_and_refund(None, None, None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_make_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Synchronizes a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_auto_captured_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Charged,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
encoded_data: None,
|
||||
capture_method: None,
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged,);
|
||||
}
|
||||
|
||||
// Refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_auto_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(None, None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Partially refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_succeeded_payment() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
None,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
refund_response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_succeeded_payment_multiple_times() {
|
||||
CONNECTOR
|
||||
.make_payment_and_multiple_refund(
|
||||
None,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Synchronizes a refund using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_refund() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(None, None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Cards Negative scenerios
|
||||
// Creates a payment with incorrect card number.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_card_number() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||
card_number: Secret::new("1891011".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(x.message, "Invalid parameter",);
|
||||
assert_eq!(x.reason, Some("card.number".to_string()));
|
||||
}
|
||||
|
||||
// Creates a payment with empty card number.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_empty_card_number() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||
card_number: Secret::new(String::from("")),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(x.message, "Invalid parameter",);
|
||||
assert_eq!(x.reason, Some("card.number".to_string()));
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect CVC.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_cvc() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||
card_cvc: Secret::new("1ad2345".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(x.message, "Invalid parameter",);
|
||||
assert_eq!(x.reason, Some("card.cvv".to_string()));
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect expiry month.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_invalid_exp_month() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||
card_exp_month: Secret::new("201".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(x.message, "Invalid parameter",);
|
||||
assert_eq!(x.reason, Some("card.expiration_month".to_string()));
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect expiry year.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||
card_exp_year: Secret::new("20001".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(x.message, "Invalid parameter",);
|
||||
assert_eq!(x.reason, Some("card.expiration_year".to_string()));
|
||||
}
|
||||
|
||||
// Voids a payment using automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_fail_void_payment_for_auto_capture() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
let void_response = CONNECTOR
|
||||
.void_payment(txn_id.unwrap(), None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = void_response.response.unwrap_err();
|
||||
assert_eq!(x.code, "5021",);
|
||||
assert_eq!(x.message, "Acquirer could not process the request");
|
||||
}
|
||||
|
||||
// Captures a payment using invalid connector payment id.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_capture_for_invalid_payment() {
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment("123456sdf789".to_string(), None, Some(get_payment_info()))
|
||||
.await
|
||||
.unwrap();
|
||||
let x = capture_response.response.unwrap_err();
|
||||
assert_eq!(x.code, "3003",);
|
||||
}
|
||||
|
||||
// Refunds a payment with refund amount higher than payment amount.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
None,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 150,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
Some(get_payment_info()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
println!("response from refund amount higher payment");
|
||||
println!("{}", x.code);
|
||||
assert_eq!(x.code, "5007",);
|
||||
assert_eq!(x.message, "Amount exceeded",);
|
||||
}
|
||||
|
||||
pub fn get_payment_info() -> PaymentInfo {
|
||||
PaymentInfo {
|
||||
address: Some(PaymentAddress {
|
||||
shipping: None,
|
||||
billing: Some(Address {
|
||||
phone: None,
|
||||
address: Some(api::AddressDetails {
|
||||
city: None,
|
||||
country: Some("PA".to_string()),
|
||||
line1: None,
|
||||
line2: None,
|
||||
line3: None,
|
||||
zip: None,
|
||||
state: None,
|
||||
first_name: None,
|
||||
last_name: None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
auth_type: None,
|
||||
access_token: None,
|
||||
router_return_url: None,
|
||||
}
|
||||
}
|
||||
// Connector dependent test cases goes here
|
||||
|
||||
// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests
|
||||
@ -6,6 +6,7 @@ mod authorizedotnet;
|
||||
mod checkout;
|
||||
mod connector_auth;
|
||||
mod cybersource;
|
||||
mod dlocal;
|
||||
mod fiserv;
|
||||
mod globalpay;
|
||||
mod payu;
|
||||
|
||||
@ -48,3 +48,8 @@ api_secret = "MySecretKey"
|
||||
key1 = "Merchant Id"
|
||||
api_key = "API Key"
|
||||
api_secret = "API Secret Key"
|
||||
|
||||
[dlocal]
|
||||
key1 = "key1"
|
||||
api_key = "api_key"
|
||||
api_secret = "secret"
|
||||
@ -85,6 +85,9 @@ base_url = "https://apple-pay-gateway.apple.com/"
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.dlocal]
|
||||
base_url = "https://sandbox.dlocal.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "cybersource", "shift4", "worldpay", "globalpay"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "cybersource", "shift4", "worldpay", "globalpay", "dlocal"]
|
||||
|
||||
Reference in New Issue
Block a user