mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(connector): [Trustpay] add authorize (cards 3ds, no3ds and bank redirects), refund, psync, rsync (#717)
Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in> Co-authored-by: sai harsha <sai.harsha@sai.harsha-MacBookPro> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
@ -65,6 +65,7 @@ cards = [
|
||||
"stripe",
|
||||
"worldline",
|
||||
"worldpay",
|
||||
"trustpay",
|
||||
]
|
||||
|
||||
[refund]
|
||||
@ -103,6 +104,8 @@ shift4.base_url = "https://api.shift4.com/"
|
||||
stripe.base_url = "https://api.stripe.com/"
|
||||
worldline.base_url = "https://eu.sandbox.api-ingenico.com/"
|
||||
worldpay.base_url = "https://try.access.worldpay.com/"
|
||||
trustpay.base_url = "https://test-tpgw.trustpay.eu/"
|
||||
trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
|
||||
|
||||
[scheduler]
|
||||
stream = "SCHEDULER_STREAM"
|
||||
|
||||
@ -144,6 +144,8 @@ shift4.base_url = "https://api.shift4.com/"
|
||||
stripe.base_url = "https://api.stripe.com/"
|
||||
worldline.base_url = "https://eu.sandbox.api-ingenico.com/"
|
||||
worldpay.base_url = "https://try.access.worldpay.com/"
|
||||
trustpay.base_url = "https://test-tpgw.trustpay.eu/"
|
||||
trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
|
||||
|
||||
# This data is used to call respective connectors for wallets and cards
|
||||
[connectors.supported]
|
||||
|
||||
@ -89,6 +89,8 @@ shift4.base_url = "https://api.shift4.com/"
|
||||
stripe.base_url = "https://api.stripe.com/"
|
||||
worldline.base_url = "https://eu.sandbox.api-ingenico.com/"
|
||||
worldpay.base_url = "https://try.access.worldpay.com/"
|
||||
trustpay.base_url = "https://test-tpgw.trustpay.eu/"
|
||||
trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
@ -112,6 +114,7 @@ cards = [
|
||||
"stripe",
|
||||
"worldline",
|
||||
"worldpay",
|
||||
"trustpay",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -539,6 +539,7 @@ pub enum MandateStatus {
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
frunk::LabelledGeneric,
|
||||
Hash,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@ -567,11 +568,18 @@ pub enum Connector {
|
||||
Stripe,
|
||||
Worldline,
|
||||
Worldpay,
|
||||
Trustpay,
|
||||
}
|
||||
|
||||
impl Connector {
|
||||
pub fn supports_access_token(&self) -> bool {
|
||||
matches!(self, Self::Airwallex | Self::Globalpay | Self::Payu)
|
||||
pub fn supports_access_token(&self, payment_method: PaymentMethod) -> bool {
|
||||
matches!(
|
||||
(self, payment_method),
|
||||
(Self::Airwallex, _)
|
||||
| (Self::Globalpay, _)
|
||||
| (Self::Payu, _)
|
||||
| (Self::Trustpay, PaymentMethod::BankRedirect)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -611,6 +619,7 @@ pub enum RoutableConnectors {
|
||||
Worldline,
|
||||
Worldpay,
|
||||
Multisafepay,
|
||||
Trustpay,
|
||||
}
|
||||
|
||||
/// Wallets which support obtaining session object
|
||||
|
||||
@ -44,7 +44,7 @@ where
|
||||
/// Functionality, for specifically encoding `Self` into `String`
|
||||
/// after serialization by using `serde::Serialize`
|
||||
///
|
||||
fn encode(&'e self) -> CustomResult<String, errors::ParsingError>
|
||||
fn url_encode(&'e self) -> CustomResult<String, errors::ParsingError>
|
||||
where
|
||||
Self: Serialize;
|
||||
|
||||
@ -103,7 +103,7 @@ where
|
||||
}
|
||||
|
||||
// Check without two functions can we combine this
|
||||
fn encode(&'e self) -> CustomResult<String, errors::ParsingError>
|
||||
fn url_encode(&'e self) -> CustomResult<String, errors::ParsingError>
|
||||
where
|
||||
Self: Serialize,
|
||||
{
|
||||
|
||||
@ -251,6 +251,7 @@ pub struct Connectors {
|
||||
pub stripe: ConnectorParams,
|
||||
pub worldline: ConnectorParams,
|
||||
pub worldpay: ConnectorParams,
|
||||
pub trustpay: ConnectorParamsWithMoreUrls,
|
||||
|
||||
// Keep this field separate from the remaining fields
|
||||
pub supported: SupportedConnectors,
|
||||
@ -262,6 +263,13 @@ pub struct ConnectorParams {
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
#[serde(default)]
|
||||
pub struct ConnectorParamsWithMoreUrls {
|
||||
pub base_url: String,
|
||||
pub base_url_bank_redirects: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SchedulerSettings {
|
||||
|
||||
@ -18,6 +18,7 @@ pub mod payu;
|
||||
pub mod rapyd;
|
||||
pub mod shift4;
|
||||
pub mod stripe;
|
||||
pub mod trustpay;
|
||||
pub mod utils;
|
||||
pub mod worldline;
|
||||
pub mod worldpay;
|
||||
@ -27,5 +28,6 @@ pub use self::{
|
||||
authorizedotnet::Authorizedotnet, bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree,
|
||||
checkout::Checkout, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv,
|
||||
globalpay::Globalpay, klarna::Klarna, multisafepay::Multisafepay, nuvei::Nuvei, payu::Payu,
|
||||
rapyd::Rapyd, shift4::Shift4, stripe::Stripe, worldline::Worldline, worldpay::Worldpay,
|
||||
rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay, worldline::Worldline,
|
||||
worldpay::Worldpay,
|
||||
};
|
||||
|
||||
@ -346,7 +346,7 @@ impl
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let req = stripe::PaymentIntentRequest::try_from(req)?;
|
||||
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::encode(&req)
|
||||
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::url_encode(&req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(stripe_req))
|
||||
}
|
||||
|
||||
630
crates/router/src/connector/trustpay.rs
Normal file
630
crates/router/src/connector/trustpay.rs
Normal file
@ -0,0 +1,630 @@
|
||||
mod transformers;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use base64::Engine;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use transformers as trustpay;
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
consts,
|
||||
core::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers,
|
||||
services::{self, ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
ErrorResponse, Response,
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Trustpay;
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Trustpay
|
||||
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> {
|
||||
match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => {
|
||||
let token = req
|
||||
.access_token
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
"application/json".to_owned(),
|
||||
),
|
||||
(
|
||||
headers::AUTHORIZATION.to_string(),
|
||||
format!("Bearer {}", token.token),
|
||||
),
|
||||
])
|
||||
}
|
||||
_ => {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
self.get_content_type().to_string(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
Ok(header)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Trustpay {
|
||||
fn id(&self) -> &'static str {
|
||||
"trustpay"
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
"application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||
connectors.trustpay.base_url.as_ref()
|
||||
}
|
||||
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let auth = trustpay::TrustpayAuthType::try_from(auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(headers::X_API_KEY.to_string(), auth.api_key)])
|
||||
}
|
||||
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: trustpay::TrustpayErrorResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
let default_error = trustpay::Errors {
|
||||
code: 0,
|
||||
description: consts::NO_ERROR_CODE.to_string(),
|
||||
};
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.status.to_string(),
|
||||
message: format!("{:?}", response.errors.first().unwrap_or(&default_error)),
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Trustpay {}
|
||||
|
||||
impl api::PreVerify for Trustpay {}
|
||||
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentVoid for Trustpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::ConnectorAccessToken for Trustpay {}
|
||||
|
||||
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
for Trustpay
|
||||
{
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::RefreshTokenRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
connectors.trustpay.base_url_bank_redirects, "api/oauth2/token"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefreshTokenRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let auth = trustpay::TrustpayAuthType::try_from(&req.connector_auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
let auth_value = format!(
|
||||
"Basic {}",
|
||||
consts::BASE64_ENGINE.encode(format!("{}:{}", auth.project_id, auth.secret_key))
|
||||
);
|
||||
Ok(vec![
|
||||
(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::RefreshTokenType::get_content_type(self).to_string(),
|
||||
),
|
||||
(headers::AUTHORIZATION.to_string(), auth_value),
|
||||
])
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefreshTokenRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let trustpay_req =
|
||||
utils::Encode::<trustpay::TrustpayAuthUpdateRequest>::convert_and_url_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(trustpay_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefreshTokenRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let req = Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
|
||||
.url(&types::RefreshTokenType::get_url(self, req, connectors)?)
|
||||
.body(types::RefreshTokenType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
);
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefreshTokenRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefreshTokenRouterData, errors::ConnectorError> {
|
||||
let response: trustpay::TrustpayAuthUpdateResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay TrustpayAuthUpdateResponse")
|
||||
.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> {
|
||||
let response: trustpay::TrustpayAccessTokenErrorResponse = res
|
||||
.response
|
||||
.parse_struct("Trustpay AccessTokenErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.result_info.result_code.to_string(),
|
||||
message: response.result_info.additional_info.unwrap_or_default(),
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSync for Trustpay {}
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
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 id = req.request.connector_transaction_id.clone();
|
||||
match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
|
||||
"{}{}/{}",
|
||||
connectors.trustpay.base_url_bank_redirects,
|
||||
"api/Payments/Payment",
|
||||
id.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
|
||||
)),
|
||||
_ => Ok(format!(
|
||||
"{}{}/{}",
|
||||
self.base_url(connectors),
|
||||
"api/v1/instance",
|
||||
id.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
let response: trustpay::TrustpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay PaymentsResponse")
|
||||
.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 api::PaymentCapture for Trustpay {}
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentSession for Trustpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentAuthorize for Trustpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
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> {
|
||||
match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
|
||||
"{}{}",
|
||||
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment"
|
||||
)),
|
||||
_ => Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"api/v1/purchase"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let trustpay_req = trustpay::TrustpayPaymentsRequest::try_from(req)?;
|
||||
let trustpay_req_string = match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => {
|
||||
utils::Encode::<trustpay::PaymentRequestBankRedirect>::encode_to_string_of_json(
|
||||
&trustpay_req,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?
|
||||
}
|
||||
_ => utils::Encode::<trustpay::PaymentRequestCards>::url_encode(&trustpay_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
|
||||
};
|
||||
Ok(Some(trustpay_req_string))
|
||||
}
|
||||
|
||||
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> {
|
||||
let response: trustpay::TrustpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay PaymentsResponse")
|
||||
.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 api::Refund for Trustpay {}
|
||||
impl api::RefundExecute for Trustpay {}
|
||||
impl api::RefundSync for Trustpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Trustpay
|
||||
{
|
||||
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> {
|
||||
match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
|
||||
"{}{}{}{}",
|
||||
connectors.trustpay.base_url_bank_redirects,
|
||||
"api/Payments/Payment/",
|
||||
req.request.connector_transaction_id,
|
||||
"/Refund"
|
||||
)),
|
||||
_ => Ok(format!("{}{}", self.base_url(connectors), "api/v1/Reverse")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let trustpay_req = trustpay::TrustpayRefundRequest::try_from(req)?;
|
||||
let trustpay_req_string = match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => utils::Encode::<
|
||||
trustpay::TrustpayRefundRequestBankRedirect,
|
||||
>::encode_to_string_of_json(
|
||||
&trustpay_req
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
|
||||
_ => utils::Encode::<trustpay::TrustpayRefundRequestCards>::url_encode(&trustpay_req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
|
||||
};
|
||||
Ok(Some(trustpay_req_string))
|
||||
}
|
||||
|
||||
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> {
|
||||
let response: trustpay::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay RefundResponse")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
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::RSync, types::RefundsData, types::RefundsResponseData> for Trustpay {
|
||||
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 id = req
|
||||
.request
|
||||
.connector_refund_id
|
||||
.to_owned()
|
||||
.ok_or_else(|| errors::ConnectorError::MissingConnectorRefundID)?;
|
||||
match req.payment_method {
|
||||
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
|
||||
"{}{}/{}",
|
||||
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment", id
|
||||
)),
|
||||
_ => Ok(format!(
|
||||
"{}{}/{}",
|
||||
self.base_url(connectors),
|
||||
"api/v1/instance",
|
||||
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> {
|
||||
let response: trustpay::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("trustpay RefundResponse")
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Trustpay {
|
||||
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 Trustpay {
|
||||
fn get_flow_type(
|
||||
&self,
|
||||
query_params: &str,
|
||||
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
|
||||
let query =
|
||||
serde_urlencoded::from_str::<transformers::TrustpayRedirectResponse>(query_params)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
crate::logger::debug!(trustpay_redirect_response=?query);
|
||||
Ok(query.status.map_or(
|
||||
payments::CallConnectorAction::Trigger,
|
||||
|status| match status.as_str() {
|
||||
"SuccessOk" => payments::CallConnectorAction::StatusUpdate(
|
||||
storage_models::enums::AttemptStatus::Charged,
|
||||
),
|
||||
_ => payments::CallConnectorAction::Trigger,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
1070
crates/router/src/connector/trustpay/transformers.rs
Normal file
1070
crates/router/src/connector/trustpay/transformers.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -222,6 +222,7 @@ pub enum CardIssuer {
|
||||
pub trait CardData {
|
||||
fn get_card_expiry_year_2_digit(&self) -> Secret<String>;
|
||||
fn get_card_issuer(&self) -> Result<CardIssuer, Error>;
|
||||
fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String;
|
||||
}
|
||||
|
||||
impl CardData for api::Card {
|
||||
@ -237,6 +238,15 @@ impl CardData for api::Card {
|
||||
.map(|card| card.split_whitespace().collect());
|
||||
get_card_issuer(card.peek().clone().as_str())
|
||||
}
|
||||
fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String {
|
||||
let year = self.get_card_expiry_year_2_digit();
|
||||
format!(
|
||||
"{}{}{}",
|
||||
self.card_exp_month.peek().clone(),
|
||||
delimiter,
|
||||
year.peek()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_card_issuer(card_number: &str) -> Result<CardIssuer, Error> {
|
||||
|
||||
@ -86,7 +86,7 @@ pub fn mk_add_card_request(
|
||||
name_on_card: Some("John Doe".to_string().into()), // [#256]
|
||||
nickname: Some("router".to_string()), //
|
||||
};
|
||||
let body = utils::Encode::<AddCardRequest<'_>>::encode(&add_card_req)
|
||||
let body = utils::Encode::<AddCardRequest<'_>>::url_encode(&add_card_req)
|
||||
.change_context(errors::VaultError::RequestEncodingFailed)?;
|
||||
let mut url = locker.host.to_owned();
|
||||
url.push_str("/card/addCard");
|
||||
@ -139,7 +139,7 @@ pub fn mk_get_card_request<'a>(
|
||||
card_id,
|
||||
};
|
||||
|
||||
let body = utils::Encode::<GetCard<'_>>::encode(&get_card_req)
|
||||
let body = utils::Encode::<GetCard<'_>>::url_encode(&get_card_req)
|
||||
.change_context(errors::VaultError::RequestEncodingFailed)?;
|
||||
let mut url = locker.host.to_owned();
|
||||
url.push_str("/card/getCard");
|
||||
@ -158,7 +158,7 @@ pub fn mk_delete_card_request<'a>(
|
||||
merchant_id,
|
||||
card_id,
|
||||
};
|
||||
let body = utils::Encode::<GetCard<'_>>::encode(&delete_card_req)
|
||||
let body = utils::Encode::<GetCard<'_>>::url_encode(&delete_card_req)
|
||||
.change_context(errors::VaultError::RequestEncodingFailed)?;
|
||||
let mut url = locker.host.to_owned();
|
||||
url.push_str("/card/deleteCard");
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::{
|
||||
},
|
||||
routes::AppState,
|
||||
services,
|
||||
types::{self, api as api_types, storage},
|
||||
types::{self, api as api_types, storage, transformers::ForeignInto},
|
||||
};
|
||||
|
||||
/// This function replaces the request and response type of routerdata with the
|
||||
@ -82,7 +82,10 @@ pub async fn add_access_token<
|
||||
merchant_account: &storage::MerchantAccount,
|
||||
router_data: &types::RouterData<F, Req, Res>,
|
||||
) -> RouterResult<types::AddAccessTokenResult> {
|
||||
if connector.connector_name.supports_access_token() {
|
||||
if connector
|
||||
.connector_name
|
||||
.supports_access_token(router_data.payment_method.foreign_into())
|
||||
{
|
||||
let merchant_id = &merchant_account.merchant_id;
|
||||
let store = &*state.store;
|
||||
let old_access_token = store
|
||||
|
||||
@ -182,6 +182,7 @@ impl ConnectorData {
|
||||
"worldline" => Ok(Box::new(&connector::Worldline)),
|
||||
"worldpay" => Ok(Box::new(&connector::Worldpay)),
|
||||
"multisafepay" => Ok(Box::new(&connector::Multisafepay)),
|
||||
"trustpay" => Ok(Box::new(&connector::Trustpay)),
|
||||
_ => Err(report!(errors::ConnectorError::InvalidConnectorName)
|
||||
.attach_printable(format!("invalid connector name: {connector_name}")))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError),
|
||||
|
||||
@ -22,6 +22,7 @@ pub(crate) struct ConnectorAuthentication {
|
||||
pub stripe: Option<HeaderKey>,
|
||||
pub worldpay: Option<BodyKey>,
|
||||
pub worldline: Option<SignatureKey>,
|
||||
pub trustpay: Option<SignatureKey>,
|
||||
}
|
||||
|
||||
impl ConnectorAuthentication {
|
||||
|
||||
@ -18,6 +18,7 @@ mod payu;
|
||||
mod rapyd;
|
||||
mod shift4;
|
||||
mod stripe;
|
||||
mod trustpay;
|
||||
mod utils;
|
||||
mod worldline;
|
||||
mod worldpay;
|
||||
|
||||
245
crates/router/tests/connectors/trustpay.rs
Normal file
245
crates/router/tests/connectors/trustpay.rs
Normal file
@ -0,0 +1,245 @@
|
||||
use masking::Secret;
|
||||
use router::types::{self, api, storage::enums, BrowserInformation};
|
||||
|
||||
use crate::{
|
||||
connector_auth,
|
||||
utils::{self, ConnectorActions},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct TrustpayTest;
|
||||
impl ConnectorActions for TrustpayTest {}
|
||||
impl utils::Connector for TrustpayTest {
|
||||
fn get_data(&self) -> types::api::ConnectorData {
|
||||
use router::connector::Trustpay;
|
||||
types::api::ConnectorData {
|
||||
connector: Box::new(&Trustpay),
|
||||
connector_name: types::Connector::Trustpay,
|
||||
get_token: types::api::GetToken::Connector,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||
types::ConnectorAuthType::from(
|
||||
connector_auth::ConnectorAuthentication::new()
|
||||
.trustpay
|
||||
.expect("Missing connector authentication configuration"),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"trustpay".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_browser_info() -> BrowserInformation {
|
||||
BrowserInformation {
|
||||
color_depth: 24,
|
||||
java_enabled: false,
|
||||
java_script_enabled: true,
|
||||
language: "en-US".to_string(),
|
||||
screen_height: 1080,
|
||||
screen_width: 1920,
|
||||
time_zone: 3600,
|
||||
accept_header: "*".to_string(),
|
||||
user_agent: "none".to_string(),
|
||||
ip_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_payment_authorize_data() -> Option<types::PaymentsAuthorizeData> {
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("4200000000000000".to_string()),
|
||||
card_exp_year: Secret::new("25".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
browser_info: Some(get_default_browser_info()),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
})
|
||||
}
|
||||
|
||||
fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||
Some(utils::PaymentInfo {
|
||||
address: Some(types::PaymentAddress {
|
||||
billing: Some(api::Address {
|
||||
address: Some(api::AddressDetails {
|
||||
first_name: Some(Secret::new("first".to_string())),
|
||||
last_name: Some(Secret::new("last".to_string())),
|
||||
line1: Some(Secret::new("line1".to_string())),
|
||||
line2: Some(Secret::new("line2".to_string())),
|
||||
city: Some("city".to_string()),
|
||||
zip: Some(Secret::new("zip".to_string())),
|
||||
country: Some("IN".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
phone: None,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
router_return_url: Some(String::from("http://localhost:8080")),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
static CONNECTOR: TrustpayTest = TrustpayTest {};
|
||||
|
||||
// Cards Positive Tests
|
||||
// Creates a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_make_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(
|
||||
get_default_payment_authorize_data(),
|
||||
get_default_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(
|
||||
get_default_payment_authorize_data(),
|
||||
get_default_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,
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.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(
|
||||
get_default_payment_authorize_data(),
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
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(
|
||||
get_default_payment_authorize_data(),
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.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 payment_authorize_data = types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("1234567891011".to_string()),
|
||||
card_exp_year: Secret::new("25".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
browser_info: Some(get_default_browser_info()),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
};
|
||||
let response = CONNECTOR
|
||||
.make_payment(Some(payment_authorize_data), get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Errors { code: 61, description: \"invalid payment data (country or brand)\" }".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment with empty card number.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_empty_card_number() {
|
||||
let payment_authorize_data = types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("".to_string()),
|
||||
card_exp_year: Secret::new("25".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
browser_info: Some(get_default_browser_info()),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
};
|
||||
let response = CONNECTOR
|
||||
.make_payment(Some(payment_authorize_data), get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
let x = response.response.unwrap_err();
|
||||
assert_eq!(
|
||||
x.message,
|
||||
"Errors { code: 61, description: \"invalid payment data (country or brand)\" }",
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect expiry year.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
let payment_authorize_data = Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethodData::Card(api::Card {
|
||||
card_number: Secret::new("4200000000000000".to_string()),
|
||||
card_exp_year: Secret::new("22".to_string()),
|
||||
card_cvc: Secret::new("123".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
browser_info: Some(get_default_browser_info()),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
});
|
||||
let response = CONNECTOR
|
||||
.make_payment(payment_authorize_data, get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Errors { code: 15, description: \"the provided expiration year is not valid\" }"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
@ -75,6 +75,8 @@ shift4.base_url = "https://api.shift4.com/"
|
||||
stripe.base_url = "https://api.stripe.com/"
|
||||
worldline.base_url = "https://eu.sandbox.api-ingenico.com/"
|
||||
worldpay.base_url = "https://try.access.worldpay.com/"
|
||||
trustpay.base_url = "https://test-tpgw.trustpay.eu/"
|
||||
trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
@ -98,4 +100,5 @@ cards = [
|
||||
"stripe",
|
||||
"worldline",
|
||||
"worldpay",
|
||||
"trustpay",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user