mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(connector): [Stax] Implement Cards for Connector Stax (#1773)
Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com>
This commit is contained in:
@ -294,6 +294,7 @@ base_url = "" # Base url used when adding links that should redirect to self
|
||||
stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } }
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
stax = { long_lived_token = true, payment_method = "card" }
|
||||
|
||||
[dummy_connector]
|
||||
payment_ttl = 172800 # Time to live for dummy connector payment in redis
|
||||
@ -344,6 +345,14 @@ pix = { country = "BR", currency = "BRL" }
|
||||
red_compra = { country = "CL", currency = "CLP" }
|
||||
red_pagos = { country = "UY", currency = "UYU" }
|
||||
|
||||
[pm_filters.stax]
|
||||
credit = { currency = "USD" }
|
||||
debit = { currency = "USD" }
|
||||
ach = { currency = "USD" }
|
||||
|
||||
[connector_customer]
|
||||
connector_list = "stax"
|
||||
|
||||
[bank_config.online_banking_fpx]
|
||||
adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,may_bank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank"
|
||||
|
||||
|
||||
@ -298,6 +298,11 @@ debit = { not_available_flows = { capture_method = "manual" } }
|
||||
credit = { not_available_flows = { capture_method = "manual" } }
|
||||
debit = { not_available_flows = { capture_method = "manual" } }
|
||||
|
||||
[pm_filters.stax]
|
||||
credit = { currency = "USD" }
|
||||
debit = { currency = "USD" }
|
||||
ach = { currency = "USD" }
|
||||
|
||||
[pm_filters.trustpay]
|
||||
credit = { not_available_flows = { capture_method = "manual" } }
|
||||
debit = { not_available_flows = { capture_method = "manual" } }
|
||||
@ -321,10 +326,11 @@ debit = { currency = "USD" }
|
||||
[tokenization]
|
||||
stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } }
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
stax = { long_lived_token = true, payment_method = "card" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
|
||||
[connector_customer]
|
||||
connector_list = "bluesnap,stripe"
|
||||
connector_list = "bluesnap,stax,stripe"
|
||||
payout_connector_list = "wise"
|
||||
|
||||
[dummy_connector]
|
||||
|
||||
@ -190,6 +190,7 @@ consumer_group = "SCHEDULER_GROUP"
|
||||
stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } }
|
||||
checkout = { long_lived_token = false, payment_method = "wallet" }
|
||||
mollie = {long_lived_token = false, payment_method = "card"}
|
||||
stax = { long_lived_token = true, payment_method = "card" }
|
||||
|
||||
[dummy_connector]
|
||||
payment_ttl = 172800
|
||||
@ -226,6 +227,11 @@ pix = { country = "BR", currency = "BRL" }
|
||||
red_compra = { country = "CL", currency = "CLP" }
|
||||
red_pagos = { country = "UY", currency = "UYU" }
|
||||
|
||||
[pm_filters.stax]
|
||||
credit = { currency = "USD" }
|
||||
debit = { currency = "USD" }
|
||||
ach = { currency = "USD" }
|
||||
|
||||
[bank_config.online_banking_fpx]
|
||||
adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,may_bank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank"
|
||||
|
||||
@ -239,3 +245,6 @@ wallet.apple_pay = {connector_list = "stripe,adyen"}
|
||||
wallet.paypal = {connector_list = "adyen"}
|
||||
card.credit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"}
|
||||
card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"}
|
||||
|
||||
[connector_customer]
|
||||
connector_list = "stax"
|
||||
|
||||
@ -3,10 +3,13 @@ mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::PeekInterface;
|
||||
use transformers as stax;
|
||||
|
||||
use super::utils::{to_connector_meta, RefundsRequestData};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
consts,
|
||||
core::errors::{self, CustomResult},
|
||||
headers,
|
||||
services::{
|
||||
@ -38,16 +41,6 @@ impl api::RefundExecute for Stax {}
|
||||
impl api::RefundSync for Stax {}
|
||||
impl api::PaymentToken for Stax {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::PaymentMethodToken,
|
||||
types::PaymentMethodTokenizationData,
|
||||
types::PaymentsResponseData,
|
||||
> for Stax
|
||||
{
|
||||
// Not Implemented (R)
|
||||
}
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Stax
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
@ -95,9 +88,10 @@ impl ConnectorCommon for Stax {
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let auth = stax::StaxAuthType::try_from(auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
|
||||
Ok(vec![(
|
||||
headers::AUTHORIZATION.to_string(),
|
||||
auth.api_key.into_masked(),
|
||||
format!("Bearer {}", auth.api_key.peek()).into_masked(),
|
||||
)])
|
||||
}
|
||||
|
||||
@ -105,20 +99,196 @@ impl ConnectorCommon for Stax {
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: stax::StaxErrorResponse = res
|
||||
.response
|
||||
.parse_struct("StaxErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.code,
|
||||
message: response.message,
|
||||
reason: response.reason,
|
||||
code: consts::NO_ERROR_CODE.to_string(),
|
||||
message: consts::NO_ERROR_MESSAGE.to_string(),
|
||||
reason: Some(
|
||||
std::str::from_utf8(&res.response)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?
|
||||
.to_owned(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::ConnectorCustomer for Stax {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::CreateConnectorCustomer,
|
||||
types::ConnectorCustomerData,
|
||||
types::PaymentsResponseData,
|
||||
> for Stax
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::ConnectorCustomerRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<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::ConnectorCustomerRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}customer", self.base_url(connectors),))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::ConnectorCustomerRouterData,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
let connector_request = stax::StaxCustomerRequest::try_from(req)?;
|
||||
|
||||
let stax_req = types::RequestBody::log_and_get_request_body(
|
||||
&connector_request,
|
||||
utils::Encode::<stax::StaxCustomerRequest>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(stax_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::ConnectorCustomerRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::ConnectorCustomerType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::ConnectorCustomerType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::ConnectorCustomerType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::ConnectorCustomerRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::ConnectorCustomerRouterData, errors::ConnectorError>
|
||||
where
|
||||
types::PaymentsResponseData: Clone,
|
||||
{
|
||||
let response: stax::StaxCustomerResponse = res
|
||||
.response
|
||||
.parse_struct("StaxCustomerResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::PaymentMethodToken,
|
||||
types::PaymentMethodTokenizationData,
|
||||
types::PaymentsResponseData,
|
||||
> for Stax
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::TokenizationRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<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::TokenizationRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}payment-method/", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::TokenizationRouterData,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
let connector_request = stax::StaxTokenRequest::try_from(req)?;
|
||||
|
||||
let stax_req = types::RequestBody::log_and_get_request_body(
|
||||
&connector_request,
|
||||
utils::Encode::<stax::StaxTokenRequest>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(stax_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::TokenizationRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::TokenizationType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::TokenizationType::get_headers(self, req, connectors)?)
|
||||
.body(types::TokenizationType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::TokenizationRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::TokenizationRouterData, errors::ConnectorError>
|
||||
where
|
||||
types::PaymentsResponseData: Clone,
|
||||
{
|
||||
let response: stax::StaxTokenResponse = res
|
||||
.response
|
||||
.parse_struct("StaxTokenResponse")
|
||||
.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)
|
||||
}
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Stax
|
||||
{
|
||||
@ -153,9 +323,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
Ok(format!("{}charge", self.base_url(connectors),))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -163,6 +333,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
let req_obj = stax::StaxPaymentsRequest::try_from(req)?;
|
||||
|
||||
let stax_req = types::RequestBody::log_and_get_request_body(
|
||||
&req_obj,
|
||||
utils::Encode::<stax::StaxPaymentsRequest>::encode_to_string_of_json,
|
||||
@ -198,7 +369,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||
let response: stax::StaxPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Stax PaymentsAuthorizeResponse")
|
||||
.parse_struct("StaxPaymentsAuthorizeResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -232,10 +403,19 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
let connector_payment_id = req
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||
|
||||
Ok(format!(
|
||||
"{}/transaction/{connector_payment_id}",
|
||||
self.base_url(connectors),
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@ -260,7 +440,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: stax::StaxPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("stax PaymentsSyncResponse")
|
||||
.parse_struct("StaxPaymentsSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -294,17 +474,27 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
Ok(format!(
|
||||
"{}/transaction/{}/capture",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_transaction_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
|
||||
let connector_req = stax::StaxCaptureRequest::try_from(req)?;
|
||||
let stax_req = types::RequestBody::log_and_get_request_body(
|
||||
&connector_req,
|
||||
utils::Encode::<stax::StaxCaptureRequest>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(stax_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@ -332,7 +522,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||
let response: stax::StaxPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Stax PaymentsCaptureResponse")
|
||||
.parse_struct("StaxPaymentsCaptureResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -352,6 +542,67 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Stax
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<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> {
|
||||
Ok(format!(
|
||||
"{}/transaction/{}/void-or-refund",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_transaction_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)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCancelRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
|
||||
let response: stax::StaxPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("StaxPaymentsVoidResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
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 Stax {
|
||||
@ -369,10 +620,22 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefundsRouterData<api::Execute>,
|
||||
_connectors: &settings::Connectors,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
let connector_transaction_id = if req.request.connector_metadata.is_some() {
|
||||
let stax_capture: stax::StaxMetaData =
|
||||
to_connector_meta(req.request.connector_metadata.clone())?;
|
||||
stax_capture.capture_id
|
||||
} else {
|
||||
req.request.connector_transaction_id.clone()
|
||||
};
|
||||
|
||||
Ok(format!(
|
||||
"{}/transaction/{}/refund",
|
||||
self.base_url(connectors),
|
||||
connector_transaction_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -412,7 +675,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
|
||||
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
|
||||
let response: stax::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("stax RefundResponse")
|
||||
.parse_struct("StaxRefundResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -444,10 +707,14 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefundSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
Ok(format!(
|
||||
"{}/transaction/{}",
|
||||
self.base_url(connectors),
|
||||
req.request.get_connector_refund_id()?,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@ -461,7 +728,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::RefundSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
@ -471,10 +737,10 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
data: &types::RefundSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||
let response: stax::RefundResponse =
|
||||
res.response
|
||||
.parse_struct("stax RefundSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
let response: stax::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("StaxRefundSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
|
||||
@ -1,45 +1,40 @@
|
||||
use masking::Secret;
|
||||
use common_utils::pii::Email;
|
||||
use error_stack::IntoReport;
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::PaymentsAuthorizeRequestData,
|
||||
connector::utils::{CardData, PaymentsAuthorizeRequestData, RouterData},
|
||||
core::errors,
|
||||
types::{self, api, storage::enums},
|
||||
};
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct StaxPaymentsRequest {
|
||||
amount: i64,
|
||||
card: StaxCard,
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StaxPaymentsRequestMetaData {
|
||||
tax: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct StaxCard {
|
||||
name: Secret<String>,
|
||||
number: cards::CardNumber,
|
||||
expiry_month: Secret<String>,
|
||||
expiry_year: Secret<String>,
|
||||
cvc: Secret<String>,
|
||||
complete: bool,
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StaxPaymentsRequest {
|
||||
payment_method_id: Secret<String>,
|
||||
total: i64,
|
||||
is_refundable: bool,
|
||||
pre_auth: bool,
|
||||
meta: StaxPaymentsRequestMetaData,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for StaxPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(req_card) => {
|
||||
let card = StaxCard {
|
||||
name: req_card.card_holder_name,
|
||||
number: req_card.card_number,
|
||||
expiry_month: req_card.card_exp_month,
|
||||
expiry_year: req_card.card_exp_year,
|
||||
cvc: req_card.card_cvc,
|
||||
complete: item.request.is_auto_capture()?,
|
||||
};
|
||||
api::PaymentMethodData::Card(_) => {
|
||||
let pre_auth = !item.request.is_auto_capture()?;
|
||||
Ok(Self {
|
||||
amount: item.request.amount,
|
||||
card,
|
||||
meta: StaxPaymentsRequestMetaData { tax: 0 },
|
||||
total: item.request.amount,
|
||||
is_refundable: true,
|
||||
pre_auth,
|
||||
payment_method_id: Secret::new(item.get_payment_method_token()?),
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
@ -47,7 +42,6 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for StaxPaymentsRequest {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
// Auth Struct
|
||||
pub struct StaxAuthType {
|
||||
pub(super) api_key: Secret<String>,
|
||||
@ -64,34 +58,153 @@ impl TryFrom<&types::ConnectorAuthType> for StaxAuthType {
|
||||
}
|
||||
}
|
||||
}
|
||||
// PaymentsResponse
|
||||
//TODO: Append the remaining status flags
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum StaxPaymentStatus {
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StaxCustomerRequest {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
email: Option<Email>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
firstname: Option<String>,
|
||||
}
|
||||
|
||||
impl From<StaxPaymentStatus> for enums::AttemptStatus {
|
||||
fn from(item: StaxPaymentStatus) -> Self {
|
||||
match item {
|
||||
StaxPaymentStatus::Succeeded => Self::Charged,
|
||||
StaxPaymentStatus::Failed => Self::Failure,
|
||||
StaxPaymentStatus::Processing => Self::Authorizing,
|
||||
impl TryFrom<&types::ConnectorCustomerRouterData> for StaxCustomerRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::ConnectorCustomerRouterData) -> Result<Self, Self::Error> {
|
||||
if item.request.email.is_none() && item.request.name.is_none() {
|
||||
Err(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "email or name",
|
||||
})
|
||||
.into_report()
|
||||
} else {
|
||||
Ok(Self {
|
||||
email: item.request.email.to_owned(),
|
||||
firstname: item.request.name.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaxPaymentsResponse {
|
||||
status: StaxPaymentStatus,
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StaxCustomerResponse {
|
||||
id: Secret<String>,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, StaxCustomerResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, StaxCustomerResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: Ok(types::PaymentsResponseData::ConnectorCustomerResponse {
|
||||
connector_customer_id: item.response.id.expose(),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StaxTokenizeData {
|
||||
person_name: Secret<String>,
|
||||
card_number: cards::CardNumber,
|
||||
card_exp: Secret<String>,
|
||||
card_cvv: Secret<String>,
|
||||
customer_id: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "method")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum StaxTokenRequest {
|
||||
Card(StaxTokenizeData),
|
||||
}
|
||||
|
||||
impl TryFrom<&types::TokenizationRouterData> for StaxTokenRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
|
||||
let customer_id = item.get_connector_customer_id()?;
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(card_data) => {
|
||||
let stax_card_data = StaxTokenizeData {
|
||||
card_exp: card_data
|
||||
.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()),
|
||||
person_name: card_data.card_holder_name,
|
||||
card_number: card_data.card_number,
|
||||
card_cvv: card_data.card_cvc,
|
||||
customer_id: Secret::new(customer_id),
|
||||
};
|
||||
Ok(Self::Card(stax_card_data))
|
||||
}
|
||||
api::PaymentMethodData::BankDebit(_)
|
||||
| api::PaymentMethodData::Wallet(_)
|
||||
| api::PaymentMethodData::PayLater(_)
|
||||
| api::PaymentMethodData::BankRedirect(_)
|
||||
| api::PaymentMethodData::BankTransfer(_)
|
||||
| api::PaymentMethodData::Crypto(_)
|
||||
| api::PaymentMethodData::MandatePayment
|
||||
| api::PaymentMethodData::Reward(_)
|
||||
| api::PaymentMethodData::Voucher(_)
|
||||
| api::PaymentMethodData::GiftCard(_)
|
||||
| api::PaymentMethodData::Upi(_) => Err(errors::ConnectorError::NotImplemented(
|
||||
"Payment Method".to_string(),
|
||||
))
|
||||
.into_report(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StaxTokenResponse {
|
||||
id: Secret<String>,
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, StaxTokenResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, StaxTokenResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: Ok(types::PaymentsResponseData::TokenizationResponse {
|
||||
token: item.response.id.expose(),
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StaxPaymentResponseTypes {
|
||||
Charge,
|
||||
PreAuth,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StaxChildCapture {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct StaxPaymentsResponse {
|
||||
success: bool,
|
||||
id: String,
|
||||
is_captured: i8,
|
||||
is_voided: bool,
|
||||
child_captures: Vec<StaxChildCapture>,
|
||||
#[serde(rename = "type")]
|
||||
payment_response_type: StaxPaymentResponseTypes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct StaxMetaData {
|
||||
pub capture_id: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, StaxPaymentsResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
@ -100,13 +213,36 @@ impl<F, T>
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, StaxPaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let mut connector_metadata = None;
|
||||
let mut status = match item.response.success {
|
||||
true => match item.response.payment_response_type {
|
||||
StaxPaymentResponseTypes::Charge => enums::AttemptStatus::Charged,
|
||||
StaxPaymentResponseTypes::PreAuth => match item.response.is_captured {
|
||||
0 => enums::AttemptStatus::Authorized,
|
||||
_ => {
|
||||
connector_metadata =
|
||||
item.response.child_captures.first().map(|child_captures| {
|
||||
serde_json::json!(StaxMetaData {
|
||||
capture_id: child_captures.id.clone()
|
||||
})
|
||||
});
|
||||
enums::AttemptStatus::Charged
|
||||
}
|
||||
},
|
||||
},
|
||||
false => enums::AttemptStatus::Failure,
|
||||
};
|
||||
if item.response.is_voided {
|
||||
status = enums::AttemptStatus::Voided;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
status,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
connector_metadata,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
}),
|
||||
@ -115,50 +251,48 @@ impl<F, T>
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StaxCaptureRequest {
|
||||
total: Option<i64>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsCaptureRouterData> for StaxCaptureRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
|
||||
let total = item.request.amount_to_capture;
|
||||
Ok(Self { total: Some(total) })
|
||||
}
|
||||
}
|
||||
|
||||
// REFUND :
|
||||
// Type definition for RefundRequest
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StaxRefundRequest {
|
||||
pub amount: i64,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for StaxRefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
amount: item.request.refund_amount,
|
||||
total: item.request.refund_amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Type definition for Refund Response
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
|
||||
pub enum RefundStatus {
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ChildTransactionsInResponse {
|
||||
id: String,
|
||||
success: bool,
|
||||
created_at: String,
|
||||
total: i64,
|
||||
}
|
||||
|
||||
impl From<RefundStatus> for enums::RefundStatus {
|
||||
fn from(item: RefundStatus) -> Self {
|
||||
match item {
|
||||
RefundStatus::Succeeded => Self::Success,
|
||||
RefundStatus::Failed => Self::Failure,
|
||||
RefundStatus::Processing => Self::Pending,
|
||||
//TODO: Review mapping
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RefundResponse {
|
||||
id: String,
|
||||
status: RefundStatus,
|
||||
success: bool,
|
||||
child_transactions: Vec<ChildTransactionsInResponse>,
|
||||
}
|
||||
|
||||
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
|
||||
@ -168,10 +302,32 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let filtered_txn: Vec<&ChildTransactionsInResponse> = item
|
||||
.response
|
||||
.child_transactions
|
||||
.iter()
|
||||
.filter(|txn| txn.total == item.data.request.refund_amount)
|
||||
.collect();
|
||||
|
||||
let mut refund_txn = filtered_txn
|
||||
.first()
|
||||
.ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
|
||||
|
||||
for child in filtered_txn.iter() {
|
||||
if child.created_at > refund_txn.created_at {
|
||||
refund_txn = child;
|
||||
}
|
||||
}
|
||||
|
||||
let refund_status = match refund_txn.success {
|
||||
true => enums::RefundStatus::Success,
|
||||
false => enums::RefundStatus::Failure,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: item.response.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(item.response.status),
|
||||
connector_refund_id: refund_txn.id.clone(),
|
||||
refund_status,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
@ -185,21 +341,16 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
|
||||
fn try_from(
|
||||
item: types::RefundsResponseRouterData<api::RSync, RefundResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let refund_status = match item.response.success {
|
||||
true => enums::RefundStatus::Success,
|
||||
false => enums::RefundStatus::Failure,
|
||||
};
|
||||
Ok(Self {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: item.response.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(item.response.status),
|
||||
connector_refund_id: item.response.id,
|
||||
refund_status,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct StaxErrorResponse {
|
||||
pub status_code: u16,
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
@ -137,7 +137,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_complete_authorize!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Bitpay,
|
||||
@ -164,6 +163,7 @@ default_imp_for_complete_authorize!(
|
||||
connector::Payme,
|
||||
connector::Payu,
|
||||
connector::Rapyd,
|
||||
connector::Stax,
|
||||
connector::Stripe,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
@ -201,7 +201,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_create_customer!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -275,7 +274,6 @@ impl<const T: u8> services::ConnectorRedirectResponse for connector::DummyConnec
|
||||
}
|
||||
|
||||
default_imp_for_connector_redirect_response!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Bitpay,
|
||||
@ -302,6 +300,7 @@ default_imp_for_connector_redirect_response!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Tsys,
|
||||
connector::Wise,
|
||||
connector::Worldline,
|
||||
@ -320,7 +319,6 @@ macro_rules! default_imp_for_connector_request_id {
|
||||
impl<const T: u8> api::ConnectorTransactionId for connector::DummyConnector<T> {}
|
||||
|
||||
default_imp_for_connector_request_id!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -355,6 +353,7 @@ default_imp_for_connector_request_id!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Stripe,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
@ -395,7 +394,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_accept_dispute!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -430,6 +428,7 @@ default_imp_for_accept_dispute!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Stripe,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
@ -490,7 +489,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_file_upload!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -525,6 +523,7 @@ default_imp_for_file_upload!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
connector::Opennode,
|
||||
@ -562,7 +561,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_submit_evidence!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -597,6 +595,7 @@ default_imp_for_submit_evidence!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
connector::Opennode,
|
||||
@ -634,7 +633,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_defend_dispute!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -668,8 +666,9 @@ default_imp_for_defend_dispute!(
|
||||
connector::Payu,
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Stripe,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Stripe,
|
||||
connector::Trustpay,
|
||||
connector::Tsys,
|
||||
connector::Opennode,
|
||||
@ -707,7 +706,6 @@ impl<const T: u8>
|
||||
}
|
||||
|
||||
default_imp_for_pre_processing_steps!(
|
||||
connector::Stax,
|
||||
connector::Aci,
|
||||
connector::Adyen,
|
||||
connector::Airwallex,
|
||||
@ -744,6 +742,7 @@ default_imp_for_pre_processing_steps!(
|
||||
connector::Powertranz,
|
||||
connector::Rapyd,
|
||||
connector::Shift4,
|
||||
connector::Stax,
|
||||
connector::Tsys,
|
||||
connector::Wise,
|
||||
connector::Worldline,
|
||||
|
||||
@ -61,6 +61,8 @@ fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||
access_token: None,
|
||||
connector_meta_data: None,
|
||||
return_url: None,
|
||||
connector_customer: None,
|
||||
payment_method_token: None,
|
||||
country: None,
|
||||
currency: None,
|
||||
payout_method_data: None,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use masking::Secret;
|
||||
use router::types::{self, api, storage::enums};
|
||||
use router::types::{self, api, storage::enums, PaymentsResponseData};
|
||||
use test_utils::connector_auth;
|
||||
|
||||
use crate::utils::{self, ConnectorActions};
|
||||
@ -32,12 +34,72 @@ impl utils::Connector for StaxTest {
|
||||
|
||||
static CONNECTOR: StaxTest = StaxTest {};
|
||||
|
||||
fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||
None
|
||||
fn get_default_payment_info(
|
||||
connector_customer: Option<String>,
|
||||
payment_method_token: Option<String>,
|
||||
) -> Option<utils::PaymentInfo> {
|
||||
Some(utils::PaymentInfo {
|
||||
address: None,
|
||||
auth_type: None,
|
||||
access_token: None,
|
||||
connector_meta_data: None,
|
||||
return_url: None,
|
||||
connector_customer,
|
||||
payment_method_token,
|
||||
payout_method_data: None,
|
||||
currency: None,
|
||||
country: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn customer_details() -> Option<types::ConnectorCustomerData> {
|
||||
Some(types::ConnectorCustomerData {
|
||||
..utils::CustomerType::default().0
|
||||
})
|
||||
}
|
||||
|
||||
fn token_details() -> Option<types::PaymentMethodTokenizationData> {
|
||||
Some(types::PaymentMethodTokenizationData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
|
||||
card_exp_month: Secret::new("04".to_string()),
|
||||
card_exp_year: Secret::new("2027".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
browser_info: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
None
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_customer_and_get_token() -> Option<String> {
|
||||
let customer_response = CONNECTOR
|
||||
.create_connector_customer(customer_details(), get_default_payment_info(None, None))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let connector_customer_id = match customer_response.response.unwrap() {
|
||||
PaymentsResponseData::ConnectorCustomerResponse {
|
||||
connector_customer_id,
|
||||
} => Some(connector_customer_id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let token_response = CONNECTOR
|
||||
.create_connector_pm_token(
|
||||
token_details(),
|
||||
get_default_payment_info(connector_customer_id, None),
|
||||
)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
match token_response.response.unwrap() {
|
||||
PaymentsResponseData::TokenizationResponse { token } => Some(token),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Cards Positive Tests
|
||||
@ -45,7 +107,10 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
#[actix_web::test]
|
||||
async fn should_only_authorize_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.authorize_payment(
|
||||
payment_method_details(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
@ -55,7 +120,11 @@ async fn should_only_authorize_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
None,
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
@ -71,7 +140,7 @@ async fn should_partially_capture_authorized_payment() {
|
||||
amount_to_capture: 50,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
@ -82,7 +151,10 @@ async fn should_partially_capture_authorized_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_sync_authorized_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.authorize_payment(
|
||||
payment_method_details(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
@ -95,7 +167,7 @@ async fn should_sync_authorized_payment() {
|
||||
),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.expect("PSync response");
|
||||
@ -113,7 +185,7 @@ async fn should_void_authorized_payment() {
|
||||
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Void payment response");
|
||||
@ -123,15 +195,33 @@ async fn should_void_authorized_payment() {
|
||||
// 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(
|
||||
let capture_response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
|
||||
let refund_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_meta = utils::get_connector_metadata(capture_response.response);
|
||||
|
||||
let response = CONNECTOR
|
||||
.refund_payment(
|
||||
refund_txn_id,
|
||||
Some(types::RefundsData {
|
||||
connector_metadata: refund_connector_meta,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
@ -141,15 +231,30 @@ async fn should_refund_manually_captured_payment() {
|
||||
// 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(
|
||||
let capture_response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
None,
|
||||
Some(types::PaymentsCaptureData {
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
|
||||
let refund_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_meta = utils::get_connector_metadata(capture_response.response);
|
||||
|
||||
let response = CONNECTOR
|
||||
.refund_payment(
|
||||
refund_txn_id,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
connector_metadata: refund_connector_meta,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -162,21 +267,39 @@ async fn should_partially_refund_manually_captured_payment() {
|
||||
// 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(
|
||||
let capture_response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
|
||||
let refund_txn_id =
|
||||
utils::get_connector_transaction_id(capture_response.response.clone()).unwrap();
|
||||
let refund_connector_meta = utils::get_connector_metadata(capture_response.response);
|
||||
|
||||
let refund_response = CONNECTOR
|
||||
.refund_payment(
|
||||
refund_txn_id,
|
||||
Some(types::RefundsData {
|
||||
connector_metadata: refund_connector_meta,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -190,7 +313,10 @@ async fn should_sync_manually_captured_refund() {
|
||||
#[actix_web::test]
|
||||
async fn should_make_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.make_payment(
|
||||
payment_method_details(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
@ -200,7 +326,10 @@ async fn should_make_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_sync_auto_captured_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.make_payment(
|
||||
payment_method_details(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
@ -216,7 +345,7 @@ async fn should_sync_auto_captured_payment() {
|
||||
capture_method: Some(enums::CaptureMethod::Automatic),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -227,7 +356,11 @@ async fn should_sync_auto_captured_payment() {
|
||||
#[actix_web::test]
|
||||
async fn should_refund_auto_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
None,
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -246,7 +379,7 @@ async fn should_partially_refund_succeeded_payment() {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -259,23 +392,47 @@ async fn should_partially_refund_succeeded_payment() {
|
||||
// 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(
|
||||
let payment_method_token = create_customer_and_get_token().await;
|
||||
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
payment_method_details(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, payment_method_token.clone()),
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//try refund for previous payment
|
||||
let transaction_id = utils::get_connector_transaction_id(response.response).unwrap();
|
||||
for _x in 0..2 {
|
||||
tokio::time::sleep(Duration::from_secs(60)).await; // to avoid 404 error
|
||||
let refund_response = CONNECTOR
|
||||
.refund_payment(
|
||||
transaction_id.clone(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(None, payment_method_token.clone()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
refund_response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(payment_method_details(), None, get_default_payment_info())
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
None,
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
@ -283,7 +440,7 @@ async fn should_sync_refund() {
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -297,81 +454,127 @@ async fn should_sync_refund() {
|
||||
// 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 {
|
||||
let customer_response = CONNECTOR
|
||||
.create_connector_customer(customer_details(), get_default_payment_info(None, None))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let connector_customer_id = match customer_response.response.unwrap() {
|
||||
PaymentsResponseData::ConnectorCustomerResponse {
|
||||
connector_customer_id,
|
||||
} => Some(connector_customer_id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let token_response = CONNECTOR
|
||||
.create_connector_pm_token(
|
||||
Some(types::PaymentMethodTokenizationData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_cvc: Secret::new("12345".to_string()),
|
||||
card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
|
||||
card_exp_month: Secret::new("11".to_string()),
|
||||
card_exp_year: Secret::new("2027".to_string()),
|
||||
card_cvc: Secret::new("123456".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
browser_info: None,
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(connector_customer_id, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's security code is invalid.".to_string(),
|
||||
token_response.response.unwrap_err().reason,
|
||||
Some(r#"{"card_cvv":["The card cvv may not be greater than 99999."]}"#.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 {
|
||||
let customer_response = CONNECTOR
|
||||
.create_connector_customer(customer_details(), get_default_payment_info(None, None))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let connector_customer_id = match customer_response.response.unwrap() {
|
||||
PaymentsResponseData::ConnectorCustomerResponse {
|
||||
connector_customer_id,
|
||||
} => Some(connector_customer_id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let token_response = CONNECTOR
|
||||
.create_connector_pm_token(
|
||||
Some(types::PaymentMethodTokenizationData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
|
||||
card_exp_month: Secret::new("20".to_string()),
|
||||
card_exp_year: Secret::new("2027".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
browser_info: None,
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(connector_customer_id, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration month is invalid.".to_string(),
|
||||
token_response.response.unwrap_err().reason,
|
||||
Some(r#"{"validation":["Tokenization Validation Errors: Month is invalid"]}"#.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 {
|
||||
let customer_response = CONNECTOR
|
||||
.create_connector_customer(customer_details(), get_default_payment_info(None, None))
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let connector_customer_id = match customer_response.response.unwrap() {
|
||||
PaymentsResponseData::ConnectorCustomerResponse {
|
||||
connector_customer_id,
|
||||
} => Some(connector_customer_id),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let token_response = CONNECTOR
|
||||
.create_connector_pm_token(
|
||||
Some(types::PaymentMethodTokenizationData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
|
||||
card_exp_month: Secret::new("04".to_string()),
|
||||
card_exp_year: Secret::new("2000".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
browser_info: None,
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(connector_customer_id, None),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration year is invalid.".to_string(),
|
||||
token_response.response.unwrap_err().reason,
|
||||
Some(r#"{"validation":["Tokenization Validation Errors: Year is invalid"]}"#.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
// Voids a payment using automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
#[ignore = "Connector Refunds the payment on Void call for Auto Captured Payment"]
|
||||
async fn should_fail_void_payment_for_auto_capture() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.make_payment(
|
||||
payment_method_details(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.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, get_default_payment_info())
|
||||
.void_payment(txn_id.unwrap(), None, get_default_payment_info(None, None))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@ -384,12 +587,16 @@ async fn should_fail_void_payment_for_auto_capture() {
|
||||
#[actix_web::test]
|
||||
async fn should_fail_capture_for_invalid_payment() {
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment("123456789".to_string(), None, get_default_payment_info())
|
||||
.capture_payment(
|
||||
"123456789".to_string(),
|
||||
None,
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
capture_response.response.unwrap_err().message,
|
||||
String::from("No such payment_intent: '123456789'")
|
||||
capture_response.response.unwrap_err().reason,
|
||||
Some(r#"{"id":["The selected id is invalid."]}"#.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -403,13 +610,13 @@ async fn should_fail_for_refund_amount_higher_than_payment_amount() {
|
||||
refund_amount: 150,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
get_default_payment_info(None, create_customer_and_get_token().await),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Refund amount (₹1.50) is greater than charge amount (₹1.00)",
|
||||
response.response.unwrap_err().reason,
|
||||
Some(r#"{"total":["The total may not be greater than 100."]}"#.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use std::{fmt::Debug, marker::PhantomData, str::FromStr, time::Duration};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common_utils::pii::Email;
|
||||
use error_stack::Report;
|
||||
use masking::Secret;
|
||||
use router::{
|
||||
@ -36,6 +37,8 @@ pub struct PaymentInfo {
|
||||
pub access_token: Option<AccessToken>,
|
||||
pub connector_meta_data: Option<serde_json::Value>,
|
||||
pub return_url: Option<String>,
|
||||
pub connector_customer: Option<String>,
|
||||
pub payment_method_token: Option<String>,
|
||||
pub payout_method_data: Option<api::PayoutMethodData>,
|
||||
pub currency: Option<enums::Currency>,
|
||||
pub country: Option<enums::CountryAlpha2>,
|
||||
@ -70,6 +73,52 @@ pub trait ConnectorActions: Connector {
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn create_connector_customer(
|
||||
&self,
|
||||
payment_data: Option<types::ConnectorCustomerData>,
|
||||
payment_info: Option<PaymentInfo>,
|
||||
) -> Result<types::ConnectorCustomerRouterData, Report<ConnectorError>> {
|
||||
let integration = self.get_data().connector.get_connector_integration();
|
||||
let mut request = self.generate_data(
|
||||
types::ConnectorCustomerData {
|
||||
..(payment_data.unwrap_or(CustomerType::default().0))
|
||||
},
|
||||
payment_info,
|
||||
);
|
||||
let tx: oneshot::Sender<()> = oneshot::channel().0;
|
||||
let state = routes::AppState::with_storage(
|
||||
Settings::new().unwrap(),
|
||||
StorageImpl::PostgresqlTest,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
integration.execute_pretasks(&mut request, &state).await?;
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn create_connector_pm_token(
|
||||
&self,
|
||||
payment_data: Option<types::PaymentMethodTokenizationData>,
|
||||
payment_info: Option<PaymentInfo>,
|
||||
) -> Result<types::TokenizationRouterData, Report<ConnectorError>> {
|
||||
let integration = self.get_data().connector.get_connector_integration();
|
||||
let mut request = self.generate_data(
|
||||
types::PaymentMethodTokenizationData {
|
||||
..(payment_data.unwrap_or(TokenType::default().0))
|
||||
},
|
||||
payment_info,
|
||||
);
|
||||
let tx: oneshot::Sender<()> = oneshot::channel().0;
|
||||
let state = routes::AppState::with_storage(
|
||||
Settings::new().unwrap(),
|
||||
StorageImpl::PostgresqlTest,
|
||||
tx,
|
||||
)
|
||||
.await;
|
||||
integration.execute_pretasks(&mut request, &state).await?;
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
/// For initiating payments when `CaptureMethod` is set to `Automatic`
|
||||
/// This does complete the transaction without user intervention to Capture the payment
|
||||
async fn make_payment(
|
||||
@ -443,8 +492,8 @@ pub trait ConnectorActions: Connector {
|
||||
access_token: info.clone().and_then(|a| a.access_token),
|
||||
session_token: None,
|
||||
reference_id: None,
|
||||
payment_method_token: None,
|
||||
connector_customer: None,
|
||||
payment_method_token: info.clone().and_then(|a| a.payment_method_token),
|
||||
connector_customer: info.clone().and_then(|a| a.connector_customer),
|
||||
recurring_mandate_payment_data: None,
|
||||
preprocessing_id: None,
|
||||
connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
|
||||
@ -764,6 +813,8 @@ pub struct PaymentSyncType(pub types::PaymentsSyncData);
|
||||
pub struct PaymentRefundType(pub types::RefundsData);
|
||||
pub struct CCardType(pub api::Card);
|
||||
pub struct BrowserInfoType(pub types::BrowserInformation);
|
||||
pub struct CustomerType(pub types::ConnectorCustomerData);
|
||||
pub struct TokenType(pub types::PaymentMethodTokenizationData);
|
||||
|
||||
impl Default for CCardType {
|
||||
fn default() -> Self {
|
||||
@ -887,6 +938,29 @@ impl Default for PaymentRefundType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CustomerType {
|
||||
fn default() -> Self {
|
||||
let data = types::ConnectorCustomerData {
|
||||
description: None,
|
||||
email: Some(Email::from(Secret::new("test@juspay.in".to_string()))),
|
||||
phone: None,
|
||||
name: None,
|
||||
preprocessing_id: None,
|
||||
};
|
||||
Self(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TokenType {
|
||||
fn default() -> Self {
|
||||
let data = types::PaymentMethodTokenizationData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(CCardType::default().0),
|
||||
browser_info: None,
|
||||
};
|
||||
Self(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_connector_transaction_id(
|
||||
response: Result<types::PaymentsResponseData, types::ErrorResponse>,
|
||||
) -> Option<String> {
|
||||
|
||||
Reference in New Issue
Block a user