feat(connector): [Square] Implement Card Payments for Square (#1902)

This commit is contained in:
DEEPANSHU BANSAL
2023-08-31 17:09:16 +05:30
committed by GitHub
parent ab85617935
commit c9fe389b2c
21 changed files with 1043 additions and 226 deletions

View File

@ -193,6 +193,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/"
rapyd.base_url = "https://sandboxapi.rapyd.net" rapyd.base_url = "https://sandboxapi.rapyd.net"
shift4.base_url = "https://api.shift4.com/" shift4.base_url = "https://api.shift4.com/"
square.base_url = "https://connect.squareupsandbox.com/" square.base_url = "https://connect.squareupsandbox.com/"
square.secondary_base_url = "https://pci-connect.squareupsandbox.com/"
stax.base_url = "https://apiprod.fattlabs.com/" stax.base_url = "https://apiprod.fattlabs.com/"
stripe.base_url = "https://api.stripe.com/" stripe.base_url = "https://api.stripe.com/"
stripe.base_url_file_upload = "https://files.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/"
@ -306,6 +307,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
checkout = { long_lived_token = false, payment_method = "wallet" } checkout = { long_lived_token = false, payment_method = "wallet" }
mollie = {long_lived_token = false, payment_method = "card"} mollie = {long_lived_token = false, payment_method = "card"}
stax = { long_lived_token = true, payment_method = "card,bank_debit" } stax = { long_lived_token = true, payment_method = "card,bank_debit" }
square = {long_lived_token = false, payment_method = "card"}
braintree = { long_lived_token = false, payment_method = "card" } braintree = { long_lived_token = false, payment_method = "card" }
[dummy_connector] [dummy_connector]

View File

@ -168,6 +168,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/"
rapyd.base_url = "https://sandboxapi.rapyd.net" rapyd.base_url = "https://sandboxapi.rapyd.net"
shift4.base_url = "https://api.shift4.com/" shift4.base_url = "https://api.shift4.com/"
square.base_url = "https://connect.squareupsandbox.com/" square.base_url = "https://connect.squareupsandbox.com/"
square.secondary_base_url = "https://pci-connect.squareupsandbox.com/"
stax.base_url = "https://apiprod.fattlabs.com/" stax.base_url = "https://apiprod.fattlabs.com/"
stripe.base_url = "https://api.stripe.com/" stripe.base_url = "https://api.stripe.com/"
stripe.base_url_file_upload = "https://files.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/"
@ -366,6 +367,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
checkout = { long_lived_token = false, payment_method = "wallet" } checkout = { long_lived_token = false, payment_method = "wallet" }
stax = { long_lived_token = true, payment_method = "card,bank_debit" } stax = { long_lived_token = true, payment_method = "card,bank_debit" }
mollie = {long_lived_token = false, payment_method = "card"} mollie = {long_lived_token = false, payment_method = "card"}
square = {long_lived_token = false, payment_method = "card"}
braintree = { long_lived_token = false, payment_method = "card" } braintree = { long_lived_token = false, payment_method = "card" }
[connector_customer] [connector_customer]

View File

@ -114,6 +114,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/"
rapyd.base_url = "https://sandboxapi.rapyd.net" rapyd.base_url = "https://sandboxapi.rapyd.net"
shift4.base_url = "https://api.shift4.com/" shift4.base_url = "https://api.shift4.com/"
square.base_url = "https://connect.squareupsandbox.com/" square.base_url = "https://connect.squareupsandbox.com/"
square.secondary_base_url = "https://pci-connect.squareupsandbox.com/"
stax.base_url = "https://apiprod.fattlabs.com/" stax.base_url = "https://apiprod.fattlabs.com/"
stripe.base_url = "https://api.stripe.com/" stripe.base_url = "https://api.stripe.com/"
stripe.base_url_file_upload = "https://files.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/"
@ -197,6 +198,7 @@ stripe = { long_lived_token = false, payment_method = "wallet", payment_method_t
checkout = { long_lived_token = false, payment_method = "wallet" } checkout = { long_lived_token = false, payment_method = "wallet" }
mollie = {long_lived_token = false, payment_method = "card"} mollie = {long_lived_token = false, payment_method = "card"}
stax = { long_lived_token = true, payment_method = "card,bank_debit" } stax = { long_lived_token = true, payment_method = "card,bank_debit" }
square = {long_lived_token = false, payment_method = "card"}
braintree = { long_lived_token = false, payment_method = "card" } braintree = { long_lived_token = false, payment_method = "card" }
[dummy_connector] [dummy_connector]

View File

@ -108,7 +108,7 @@ pub enum Connector {
Powertranz, Powertranz,
Rapyd, Rapyd,
Shift4, Shift4,
// Square, added as template code for future usage, Square,
Stax, Stax,
Stripe, Stripe,
Trustpay, Trustpay,
@ -223,7 +223,7 @@ pub enum RoutableConnectors {
Powertranz, Powertranz,
Rapyd, Rapyd,
Shift4, Shift4,
//Square, added as template code for future usage Square,
Stax, Stax,
Stripe, Stripe,
Trustpay, Trustpay,

View File

@ -1,14 +1,20 @@
mod transformers; pub mod transformers;
use std::fmt::Debug; use std::fmt::Debug;
use api_models::enums;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use masking::ExposeInterface; use masking::PeekInterface;
use transformers as square; use transformers as square;
use super::utils::RefundsRequestData;
use crate::{ use crate::{
configs::settings, configs::settings,
core::errors::{self, CustomResult}, consts,
core::{
errors::{self, CustomResult},
payments,
},
headers, headers,
services::{ services::{
self, self,
@ -39,16 +45,6 @@ impl api::RefundExecute for Square {}
impl api::RefundSync for Square {} impl api::RefundSync for Square {}
impl api::PaymentToken for Square {} impl api::PaymentToken for Square {}
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Square
{
// Not Implemented (R)
}
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Square impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Square
where where
Self: ConnectorIntegration<Flow, Request, Response>, Self: ConnectorIntegration<Flow, Request, Response>,
@ -91,7 +87,7 @@ impl ConnectorCommon for Square {
.change_context(errors::ConnectorError::FailedToObtainAuthType)?; .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![( Ok(vec![(
headers::AUTHORIZATION.to_string(), headers::AUTHORIZATION.to_string(),
auth.api_key.expose().into_masked(), format!("Bearer {}", auth.api_key.peek()).into_masked(),
)]) )])
} }
@ -104,16 +100,45 @@ impl ConnectorCommon for Square {
.parse_struct("SquareErrorResponse") .parse_struct("SquareErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let mut reason_list = Vec::new();
for error_iter in response.errors.iter() {
if let Some(error) = error_iter.detail.clone() {
reason_list.push(error)
}
}
let reason = reason_list.join(" ");
Ok(ErrorResponse { Ok(ErrorResponse {
status_code: res.status_code, status_code: res.status_code,
code: response.code, code: response
message: response.message, .errors
reason: response.reason, .first()
.and_then(|error| error.code.clone())
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
message: response
.errors
.first()
.and_then(|error| error.category.clone())
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
reason: Some(reason),
}) })
} }
} }
impl ConnectorValidation for Square {} impl ConnectorValidation for Square {
fn validate_capture_method(
&self,
capture_method: Option<enums::CaptureMethod>,
) -> CustomResult<(), errors::ConnectorError> {
let capture_method = capture_method.unwrap_or_default();
match capture_method {
enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()),
enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(
super::utils::construct_not_implemented_error_report(capture_method, self.id()),
),
}
}
}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData> impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Square for Square
@ -131,6 +156,230 @@ impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::Payments
{ {
} }
#[async_trait::async_trait]
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Square
{
async fn execute_pretasks(
&self,
router_data: &mut types::TokenizationRouterData,
app_state: &crate::routes::AppState,
) -> CustomResult<(), errors::ConnectorError> {
let integ: Box<
&(dyn ConnectorIntegration<
api::AuthorizeSessionToken,
types::AuthorizeSessionTokenData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);
let authorize_session_token_data = types::AuthorizeSessionTokenData {
connector_transaction_id: router_data.payment_id.clone(),
amount_to_capture: None,
currency: router_data.request.currency,
amount: router_data.request.amount,
};
let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::from((
&router_data.to_owned(),
authorize_session_token_data,
));
let resp = services::execute_connector_processing_step(
app_state,
integ,
authorize_data,
payments::CallConnectorAction::Trigger,
None,
)
.await?;
router_data.session_token = resp.session_token;
Ok(())
}
fn get_headers(
&self,
_req: &types::TokenizationRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::TokenizationType::get_content_type(self)
.to_string()
.into(),
)])
}
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!(
"{}v2/card-nonce",
connectors
.square
.secondary_base_url
.clone()
.ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?,
))
}
fn get_request_body(
&self,
req: &types::TokenizationRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let connector_request = square::SquareTokenRequest::try_from(req)?;
let square_req = types::RequestBody::log_and_get_request_body(
&connector_request,
utils::Encode::<square::SquareTokenRequest>::encode_to_string_of_json,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(square_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: square::SquareTokenResponse = res
.response
.parse_struct("SquareTokenResponse")
.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::AuthorizeSessionToken,
types::AuthorizeSessionTokenData,
types::PaymentsResponseData,
> for Square
{
fn get_headers(
&self,
_req: &types::PaymentsAuthorizeSessionTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsAuthorizeType::get_content_type(self)
.to_string()
.into(),
)])
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::PaymentsAuthorizeSessionTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let auth = square::SquareAuthType::try_from(&req.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(format!(
"{}payments/hydrate?applicationId={}",
connectors
.square
.secondary_base_url
.clone()
.ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?,
auth.key1.peek()
))
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeSessionTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::PaymentsPreAuthorizeType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(types::PaymentsPreAuthorizeType::get_headers(
self, req, connectors,
)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeSessionTokenRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeSessionTokenRouterData, errors::ConnectorError> {
let response: square::SquareSessionResponse = res
.response
.parse_struct("SquareSessionResponse")
.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::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData> impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Square for Square
{ {
@ -149,9 +398,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
fn get_url( fn get_url(
&self, &self,
_req: &types::PaymentsAuthorizeRouterData, _req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) Ok(format!("{}v2/payments", self.base_url(connectors)))
} }
fn get_request_body( fn get_request_body(
@ -159,6 +408,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData, req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> { ) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let req_obj = square::SquarePaymentsRequest::try_from(req)?; let req_obj = square::SquarePaymentsRequest::try_from(req)?;
let square_req = types::RequestBody::log_and_get_request_body( let square_req = types::RequestBody::log_and_get_request_body(
&req_obj, &req_obj,
utils::Encode::<square::SquarePaymentsRequest>::encode_to_string_of_json, utils::Encode::<square::SquarePaymentsRequest>::encode_to_string_of_json,
@ -195,7 +445,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> { ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: square::SquarePaymentsResponse = res let response: square::SquarePaymentsResponse = res
.response .response
.parse_struct("Square PaymentsAuthorizeResponse") .parse_struct("SquarePaymentsAuthorizeResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::try_from(types::ResponseRouterData {
response, response,
@ -229,10 +479,19 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
fn get_url( fn get_url(
&self, &self,
_req: &types::PaymentsSyncRouterData, req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> 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!(
"{}v2/payments/{connector_payment_id}",
self.base_url(connectors),
))
} }
fn build_request( fn build_request(
@ -257,7 +516,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> { ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: square::SquarePaymentsResponse = res let response: square::SquarePaymentsResponse = res
.response .response
.parse_struct("square PaymentsSyncResponse") .parse_struct("SquarePaymentsSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::try_from(types::ResponseRouterData {
response, response,
@ -291,17 +550,14 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
fn get_url( fn get_url(
&self, &self,
_req: &types::PaymentsCaptureRouterData, req: &types::PaymentsCaptureRouterData,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) Ok(format!(
} "{}v2/payments/{}/complete",
self.base_url(connectors),
fn get_request_body( req.request.connector_transaction_id,
&self, ))
_req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
} }
fn build_request( fn build_request(
@ -317,7 +573,6 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
.headers(types::PaymentsCaptureType::get_headers( .headers(types::PaymentsCaptureType::get_headers(
self, req, connectors, self, req, connectors,
)?) )?)
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
.build(), .build(),
)) ))
} }
@ -329,7 +584,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> { ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
let response: square::SquarePaymentsResponse = res let response: square::SquarePaymentsResponse = res
.response .response
.parse_struct("Square PaymentsCaptureResponse") .parse_struct("SquarePaymentsCaptureResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::try_from(types::ResponseRouterData {
response, response,
@ -349,6 +604,67 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData> impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Square for Square
{ {
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!(
"{}v2/payments/{}/cancel",
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: square::SquarePaymentsResponse = res
.response
.parse_struct("SquarePaymentsVoidResponse")
.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 Square { impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Square {
@ -367,9 +683,9 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
fn get_url( fn get_url(
&self, &self,
_req: &types::RefundsRouterData<api::Execute>, _req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) Ok(format!("{}v2/refunds", self.base_url(connectors),))
} }
fn get_request_body( fn get_request_body(
@ -407,10 +723,10 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
data: &types::RefundsRouterData<api::Execute>, data: &types::RefundsRouterData<api::Execute>,
res: Response, res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> { ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: square::RefundResponse = let response: square::RefundResponse = res
res.response .response
.parse_struct("square RefundResponse") .parse_struct("SquareRefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::try_from(types::ResponseRouterData {
response, response,
data: data.clone(), data: data.clone(),
@ -441,10 +757,14 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
fn get_url( fn get_url(
&self, &self,
_req: &types::RefundSyncRouterData, req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors, connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) Ok(format!(
"{}v2/refunds/{}",
self.base_url(connectors),
req.request.get_connector_refund_id()?,
))
} }
fn build_request( fn build_request(
@ -458,7 +778,6 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
.url(&types::RefundSyncType::get_url(self, req, connectors)?) .url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers() .attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?) .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
.body(types::RefundSyncType::get_request_body(self, req)?)
.build(), .build(),
)) ))
} }
@ -470,8 +789,9 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> { ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: square::RefundResponse = res let response: square::RefundResponse = res
.response .response
.parse_struct("square RefundSyncResponse") .parse_struct("SquareRefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?; .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData { types::RouterData::try_from(types::ResponseRouterData {
response, response,
data: data.clone(), data: data.clone(),

View File

@ -1,95 +1,328 @@
use masking::Secret; use api_models::payments::{BankDebitData, PayLaterData, WalletData};
use error_stack::{IntoReport, ResultExt};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
connector::utils::PaymentsAuthorizeRequestData, connector::utils::{CardData, PaymentsAuthorizeRequestData, RouterData},
core::errors, core::errors,
types::{self, api, storage::enums}, types::{
self, api,
storage::{self, enums},
},
}; };
//TODO: Fill the struct with respective fields impl TryFrom<(&types::TokenizationRouterData, BankDebitData)> for SquareTokenRequest {
#[derive(Default, Debug, Serialize, Eq, PartialEq)] type Error = error_stack::Report<errors::ConnectorError>;
pub struct SquarePaymentsRequest { fn try_from(
amount: i64, value: (&types::TokenizationRouterData, BankDebitData),
card: SquareCard, ) -> Result<Self, Self::Error> {
let (item, bank_debit_data) = value;
match bank_debit_data {
BankDebitData::AchBankDebit { .. } => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))
.into_report(),
_ => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_data),
connector: "Square",
})?,
}
}
} }
#[derive(Default, Debug, Serialize, Eq, PartialEq)] impl TryFrom<(&types::TokenizationRouterData, api_models::payments::Card)> for SquareTokenRequest {
pub struct SquareCard { type Error = error_stack::Report<errors::ConnectorError>;
name: Secret<String>, fn try_from(
value: (&types::TokenizationRouterData, api_models::payments::Card),
) -> Result<Self, Self::Error> {
let (item, card_data) = value;
let auth = SquareAuthType::try_from(&item.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let exp_year = Secret::new(
card_data
.get_expiry_year_4_digit()
.peek()
.parse::<u16>()
.into_report()
.change_context(errors::ConnectorError::DateFormattingFailed)?,
);
let exp_month = Secret::new(
card_data
.card_exp_month
.peek()
.parse::<u16>()
.into_report()
.change_context(errors::ConnectorError::DateFormattingFailed)?,
);
//The below error will never happen because if session-id is not generated it would give error in execute_pretasks itself.
let session_id = Secret::new(
item.session_token
.clone()
.ok_or(errors::ConnectorError::RequestEncodingFailed)?,
);
Ok(Self::Card(SquareTokenizeData {
client_id: auth.key1,
session_id,
card_data: SquareCardData {
exp_year,
exp_month,
number: card_data.card_number,
cvv: card_data.card_cvc,
},
}))
}
}
impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
value: (&types::TokenizationRouterData, PayLaterData),
) -> Result<Self, Self::Error> {
let (item, pay_later_data) = value;
match pay_later_data {
PayLaterData::AfterpayClearpayRedirect { .. } => Err(
errors::ConnectorError::NotImplemented("Payment Method".to_string()),
)
.into_report(),
_ => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_data),
connector: "Square",
})?,
}
}
}
impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(value: (&types::TokenizationRouterData, WalletData)) -> Result<Self, Self::Error> {
let (item, wallet_data) = value;
match wallet_data {
WalletData::ApplePay(_) => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))
.into_report(),
WalletData::GooglePay(_) => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))
.into_report(),
_ => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_data),
connector: "Square",
})?,
}
}
}
#[derive(Debug, Serialize)]
pub struct SquareCardData {
cvv: Secret<String>,
exp_month: Secret<u16>,
exp_year: Secret<u16>,
number: cards::CardNumber, number: cards::CardNumber,
expiry_month: Secret<String>, }
expiry_year: Secret<String>, #[derive(Debug, Serialize)]
cvc: Secret<String>, pub struct SquareTokenizeData {
complete: bool, client_id: Secret<String>,
session_id: Secret<String>,
card_data: SquareCardData,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum SquareTokenRequest {
Card(SquareTokenizeData),
}
impl TryFrom<&types::TokenizationRouterData> for SquareTokenRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
match item.request.payment_method_data.clone() {
api::PaymentMethodData::BankDebit(bank_debit_data) => {
Self::try_from((item, bank_debit_data))
}
api::PaymentMethodData::Card(card_data) => Self::try_from((item, card_data)),
api::PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, wallet_data)),
api::PaymentMethodData::PayLater(pay_later_data) => {
Self::try_from((item, pay_later_data))
}
api::PaymentMethodData::GiftCard(_) => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))
.into_report(),
api::PaymentMethodData::BankRedirect(_)
| api::PaymentMethodData::BankTransfer(_)
| api::PaymentMethodData::CardRedirect(_)
| api::PaymentMethodData::Crypto(_)
| api::PaymentMethodData::MandatePayment
| api::PaymentMethodData::Reward
| api::PaymentMethodData::Upi(_)
| api::PaymentMethodData::Voucher(_) => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_data),
connector: "Square",
})?,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SquareSessionResponse {
session_id: String,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, SquareSessionResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, SquareSessionResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
Ok(Self {
status: storage::enums::AttemptStatus::Pending,
session_token: Some(item.response.session_id.clone()),
response: Ok(types::PaymentsResponseData::SessionTokenResponse {
session_token: item.response.session_id,
}),
..item.data
})
}
}
#[derive(Debug, Deserialize)]
pub struct SquareTokenResponse {
card_nonce: Secret<String>,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, SquareTokenResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, SquareTokenResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::PaymentsResponseData::TokenizationResponse {
token: item.response.card_nonce.expose(),
}),
..item.data
})
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SquarePaymentsAmountData {
amount: i64,
currency: enums::Currency,
}
#[derive(Debug, Serialize)]
pub struct SquarePaymentsRequestExternalDetails {
source: String,
#[serde(rename = "type")]
source_type: String,
}
#[derive(Debug, Serialize)]
pub struct SquarePaymentsRequest {
amount_money: SquarePaymentsAmountData,
idempotency_key: Secret<String>,
source_id: Secret<String>,
autocomplete: bool,
external_details: SquarePaymentsRequestExternalDetails,
} }
impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
let autocomplete = item.request.is_auto_capture()?;
match item.request.payment_method_data.clone() { match item.request.payment_method_data.clone() {
api::PaymentMethodData::Card(req_card) => { api::PaymentMethodData::Card(_) => Ok(Self {
let card = SquareCard { idempotency_key: Secret::new(item.attempt_id.clone()),
name: req_card.card_holder_name, source_id: Secret::new(item.get_payment_method_token()?),
number: req_card.card_number, amount_money: SquarePaymentsAmountData {
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()?,
};
Ok(Self {
amount: item.request.amount, amount: item.request.amount,
card, currency: item.request.currency,
}) },
} autocomplete,
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), external_details: SquarePaymentsRequestExternalDetails {
source: "Hyperswitch".to_string(),
source_type: "Card".to_string(),
},
}),
api::PaymentMethodData::BankDebit(_)
| api::PaymentMethodData::GiftCard(_)
| api::PaymentMethodData::PayLater(_)
| api::PaymentMethodData::Wallet(_) => Err(errors::ConnectorError::NotImplemented(
"Payment Method".to_string(),
))
.into_report(),
api::PaymentMethodData::BankRedirect(_)
| api::PaymentMethodData::BankTransfer(_)
| api::PaymentMethodData::CardRedirect(_)
| api::PaymentMethodData::Crypto(_)
| api::PaymentMethodData::MandatePayment
| api::PaymentMethodData::Reward
| api::PaymentMethodData::Upi(_)
| api::PaymentMethodData::Voucher(_) => Err(errors::ConnectorError::NotSupported {
message: format!("{:?}", item.request.payment_method_data),
connector: "Square",
})?,
} }
} }
} }
//TODO: Fill the struct with respective fields
// Auth Struct // Auth Struct
pub struct SquareAuthType { pub struct SquareAuthType {
pub(super) api_key: Secret<String>, pub(super) api_key: Secret<String>,
pub(super) key1: Secret<String>,
} }
impl TryFrom<&types::ConnectorAuthType> for SquareAuthType { impl TryFrom<&types::ConnectorAuthType> for SquareAuthType {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> { fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type { match auth_type {
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { types::ConnectorAuthType::BodyKey { api_key, key1, .. } => Ok(Self {
api_key: api_key.to_owned(), api_key: api_key.to_owned(),
key1: key1.to_owned(),
}), }),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
} }
} }
} }
// PaymentsResponse // PaymentsResponse
//TODO: Append the remaining status flags #[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "UPPERCASE")]
#[serde(rename_all = "lowercase")]
pub enum SquarePaymentStatus { pub enum SquarePaymentStatus {
Succeeded, Completed,
Failed, Failed,
#[default] Approved,
Processing, Canceled,
Pending,
} }
impl From<SquarePaymentStatus> for enums::AttemptStatus { impl From<SquarePaymentStatus> for enums::AttemptStatus {
fn from(item: SquarePaymentStatus) -> Self { fn from(item: SquarePaymentStatus) -> Self {
match item { match item {
SquarePaymentStatus::Succeeded => Self::Charged, SquarePaymentStatus::Completed => Self::Charged,
SquarePaymentStatus::Approved => Self::Authorized,
SquarePaymentStatus::Failed => Self::Failure, SquarePaymentStatus::Failed => Self::Failure,
SquarePaymentStatus::Processing => Self::Authorizing, SquarePaymentStatus::Canceled => Self::Voided,
SquarePaymentStatus::Pending => Self::Pending,
} }
} }
} }
//TODO: Fill the struct with respective fields #[derive(Debug, Deserialize)]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SquarePaymentsResponseDetails {
pub struct SquarePaymentsResponse {
status: SquarePaymentStatus, status: SquarePaymentStatus,
id: String, id: String,
amount_money: SquarePaymentsAmountData,
}
#[derive(Debug, Deserialize)]
pub struct SquarePaymentsResponse {
payment: SquarePaymentsResponseDetails,
} }
impl<F, T> impl<F, T>
@ -101,64 +334,71 @@ impl<F, T>
item: types::ResponseRouterData<F, SquarePaymentsResponse, T, types::PaymentsResponseData>, item: types::ResponseRouterData<F, SquarePaymentsResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
status: enums::AttemptStatus::from(item.response.status), status: enums::AttemptStatus::from(item.response.payment.status),
response: Ok(types::PaymentsResponseData::TransactionResponse { response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), resource_id: types::ResponseId::ConnectorTransactionId(item.response.payment.id),
redirection_data: None, redirection_data: None,
mandate_reference: None, mandate_reference: None,
connector_metadata: None, connector_metadata: None,
network_txn_id: None, network_txn_id: None,
connector_response_reference_id: None, connector_response_reference_id: None,
}), }),
amount_captured: Some(item.response.payment.amount_money.amount),
..item.data ..item.data
}) })
} }
} }
//TODO: Fill the struct with respective fields
// REFUND : // REFUND :
// Type definition for RefundRequest // Type definition for RefundRequest
#[derive(Default, Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct SquareRefundRequest { pub struct SquareRefundRequest {
pub amount: i64, amount_money: SquarePaymentsAmountData,
idempotency_key: Secret<String>,
payment_id: Secret<String>,
} }
impl<F> TryFrom<&types::RefundsRouterData<F>> for SquareRefundRequest { impl<F> TryFrom<&types::RefundsRouterData<F>> for SquareRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
amount: item.request.refund_amount, amount_money: SquarePaymentsAmountData {
amount: item.request.refund_amount,
currency: item.request.currency,
},
idempotency_key: Secret::new(item.request.refund_id.clone()),
payment_id: Secret::new(item.request.connector_transaction_id.clone()),
}) })
} }
} }
// Type definition for Refund Response #[derive(Debug, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus { pub enum RefundStatus {
Succeeded, Completed,
Failed, Failed,
#[default] Pending,
Processing, Rejected,
} }
impl From<RefundStatus> for enums::RefundStatus { impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self { fn from(item: RefundStatus) -> Self {
match item { match item {
RefundStatus::Succeeded => Self::Success, RefundStatus::Completed => Self::Success,
RefundStatus::Failed => Self::Failure, RefundStatus::Failed | RefundStatus::Rejected => Self::Failure,
RefundStatus::Processing => Self::Pending, RefundStatus::Pending => Self::Pending,
//TODO: Review mapping
} }
} }
} }
//TODO: Fill the struct with respective fields #[derive(Debug, Deserialize)]
#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct SquareRefundResponseDetails {
pub struct RefundResponse {
id: String,
status: RefundStatus, status: RefundStatus,
id: String,
}
#[derive(Debug, Deserialize)]
pub struct RefundResponse {
refund: SquareRefundResponseDetails,
} }
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
@ -170,8 +410,8 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
response: Ok(types::RefundsResponseData { response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(), connector_refund_id: item.response.refund.id,
refund_status: enums::RefundStatus::from(item.response.status), refund_status: enums::RefundStatus::from(item.response.refund.status),
}), }),
..item.data ..item.data
}) })
@ -187,19 +427,21 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>>
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
Ok(Self { Ok(Self {
response: Ok(types::RefundsResponseData { response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.id.to_string(), connector_refund_id: item.response.refund.id,
refund_status: enums::RefundStatus::from(item.response.status), refund_status: enums::RefundStatus::from(item.response.refund.status),
}), }),
..item.data ..item.data
}) })
} }
} }
//TODO: Fill the struct with respective fields #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct SquareErrorDetails {
pub struct SquareErrorResponse { pub category: Option<String>,
pub status_code: u16, pub code: Option<String>,
pub code: String, pub detail: Option<String>,
pub message: String, }
pub reason: Option<String>, #[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct SquareErrorResponse {
pub errors: Vec<SquareErrorDetails>,
} }

View File

@ -1219,6 +1219,10 @@ pub(crate) fn validate_auth_type(
shift4::transformers::Shift4AuthType::try_from(val)?; shift4::transformers::Shift4AuthType::try_from(val)?;
Ok(()) Ok(())
} }
api_enums::Connector::Square => {
square::transformers::SquareAuthType::try_from(val)?;
Ok(())
}
api_enums::Connector::Stax => { api_enums::Connector::Stax => {
stax::transformers::StaxAuthType::try_from(val)?; stax::transformers::StaxAuthType::try_from(val)?;
Ok(()) Ok(())

View File

@ -59,7 +59,7 @@ pub trait Feature<F, T> {
dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>; dyn api::Connector: services::ConnectorIntegration<F, T, types::PaymentsResponseData>;
async fn add_payment_method_token<'a>( async fn add_payment_method_token<'a>(
&self, &mut self,
_state: &AppState, _state: &AppState,
_connector: &api::ConnectorData, _connector: &api::ConnectorData,
_tokenization_action: &payments::TokenizationAction, _tokenization_action: &payments::TokenizationAction,

View File

@ -118,17 +118,18 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
} }
async fn add_payment_method_token<'a>( async fn add_payment_method_token<'a>(
&self, &mut self,
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> { ) -> RouterResult<Option<String>> {
let request = self.request.clone();
tokenization::add_payment_method_token( tokenization::add_payment_method_token(
state, state,
connector, connector,
tokenization_action, tokenization_action,
self, self,
types::PaymentMethodTokenizationData::try_from(self.request.to_owned())?, types::PaymentMethodTokenizationData::try_from(request)?,
) )
.await .await
} }
@ -346,6 +347,8 @@ impl TryFrom<types::PaymentsAuthorizeData> for types::PaymentMethodTokenizationD
Ok(Self { Ok(Self {
payment_method_data: data.payment_method_data, payment_method_data: data.payment_method_data,
browser_info: data.browser_info, browser_info: data.browser_info,
currency: data.currency,
amount: Some(data.amount),
}) })
} }
} }

View File

@ -86,17 +86,18 @@ impl Feature<api::Verify, types::VerifyRequestData> for types::VerifyRouterData
} }
async fn add_payment_method_token<'a>( async fn add_payment_method_token<'a>(
&self, &mut self,
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
) -> RouterResult<Option<String>> { ) -> RouterResult<Option<String>> {
let request = self.request.clone();
tokenization::add_payment_method_token( tokenization::add_payment_method_token(
state, state,
connector, connector,
tokenization_action, tokenization_action,
self, self,
types::PaymentMethodTokenizationData::try_from(self.request.to_owned())?, types::PaymentMethodTokenizationData::try_from(request)?,
) )
.await .await
} }
@ -234,6 +235,8 @@ impl TryFrom<types::VerifyRequestData> for types::PaymentMethodTokenizationData
Ok(Self { Ok(Self {
payment_method_data: data.payment_method_data, payment_method_data: data.payment_method_data,
browser_info: None, browser_info: None,
currency: data.currency,
amount: data.amount,
}) })
} }
} }

View File

@ -211,11 +211,11 @@ pub fn create_payment_method_metadata(
})) }))
} }
pub async fn add_payment_method_token<F: Clone, T: Clone>( pub async fn add_payment_method_token<F: Clone, T: types::Tokenizable + Clone>(
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
tokenization_action: &payments::TokenizationAction, tokenization_action: &payments::TokenizationAction,
router_data: &types::RouterData<F, T, types::PaymentsResponseData>, router_data: &mut types::RouterData<F, T, types::PaymentsResponseData>,
pm_token_request_data: types::PaymentMethodTokenizationData, pm_token_request_data: types::PaymentMethodTokenizationData,
) -> RouterResult<Option<String>> { ) -> RouterResult<Option<String>> {
match tokenization_action { match tokenization_action {
@ -230,7 +230,7 @@ pub async fn add_payment_method_token<F: Clone, T: Clone>(
let pm_token_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> = let pm_token_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
Err(types::ErrorResponse::default()); Err(types::ErrorResponse::default());
let pm_token_router_data = payments::helpers::router_data_type_conversion::< let mut pm_token_router_data = payments::helpers::router_data_type_conversion::<
_, _,
api::PaymentMethodToken, api::PaymentMethodToken,
_, _,
@ -242,6 +242,16 @@ pub async fn add_payment_method_token<F: Clone, T: Clone>(
pm_token_request_data, pm_token_request_data,
pm_token_response_data, pm_token_response_data,
); );
connector_integration
.execute_pretasks(&mut pm_token_router_data, state)
.await
.to_payment_failed_response()?;
router_data
.request
.set_session_token(pm_token_router_data.session_token.clone());
let resp = services::execute_connector_processing_step( let resp = services::execute_connector_processing_step(
state, state,
connector_integration, connector_integration,

View File

@ -1129,6 +1129,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::VerifyRequestDat
Ok(Self { Ok(Self {
currency: payment_data.currency, currency: payment_data.currency,
confirm: true, confirm: true,
amount: Some(payment_data.amount.into()),
payment_method_data: payment_data payment_method_data: payment_data
.payment_method_data .payment_method_data
.get_required_value("payment_method_data")?, .get_required_value("payment_method_data")?,

View File

@ -351,7 +351,7 @@ pub struct AuthorizeSessionTokenData {
pub amount_to_capture: Option<i64>, pub amount_to_capture: Option<i64>,
pub currency: storage_enums::Currency, pub currency: storage_enums::Currency,
pub connector_transaction_id: String, pub connector_transaction_id: String,
pub amount: i64, pub amount: Option<i64>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -367,6 +367,8 @@ pub struct ConnectorCustomerData {
pub struct PaymentMethodTokenizationData { pub struct PaymentMethodTokenizationData {
pub payment_method_data: payments::PaymentMethodData, pub payment_method_data: payments::PaymentMethodData,
pub browser_info: Option<BrowserInformation>, pub browser_info: Option<BrowserInformation>,
pub currency: storage_enums::Currency,
pub amount: Option<i64>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -448,6 +450,7 @@ pub struct PaymentsSessionData {
pub struct VerifyRequestData { pub struct VerifyRequestData {
pub currency: storage_enums::Currency, pub currency: storage_enums::Currency,
pub payment_method_data: payments::PaymentMethodData, pub payment_method_data: payments::PaymentMethodData,
pub amount: Option<i64>,
pub confirm: bool, pub confirm: bool,
pub statement_descriptor_suffix: Option<String>, pub statement_descriptor_suffix: Option<String>,
pub mandate_id: Option<api_models::payments::MandateIds>, pub mandate_id: Option<api_models::payments::MandateIds>,
@ -874,7 +877,7 @@ impl From<&&mut PaymentsAuthorizeRouterData> for AuthorizeSessionTokenData {
amount_to_capture: data.amount_captured, amount_to_capture: data.amount_captured,
currency: data.request.currency, currency: data.request.currency,
connector_transaction_id: data.payment_id.clone(), connector_transaction_id: data.payment_id.clone(),
amount: data.request.amount, amount: Some(data.request.amount),
} }
} }
} }
@ -891,6 +894,40 @@ impl From<&&mut PaymentsAuthorizeRouterData> for ConnectorCustomerData {
} }
} }
impl<F> From<&RouterData<F, PaymentsAuthorizeData, PaymentsResponseData>>
for PaymentMethodTokenizationData
{
fn from(data: &RouterData<F, PaymentsAuthorizeData, PaymentsResponseData>) -> Self {
Self {
payment_method_data: data.request.payment_method_data.clone(),
browser_info: None,
currency: data.request.currency,
amount: Some(data.request.amount),
}
}
}
pub trait Tokenizable {
fn get_pm_data(&self) -> payments::PaymentMethodData;
fn set_session_token(&mut self, token: Option<String>);
}
impl Tokenizable for VerifyRequestData {
fn get_pm_data(&self) -> payments::PaymentMethodData {
self.payment_method_data.clone()
}
fn set_session_token(&mut self, _token: Option<String>) {}
}
impl Tokenizable for PaymentsAuthorizeData {
fn get_pm_data(&self) -> payments::PaymentMethodData {
self.payment_method_data.clone()
}
fn set_session_token(&mut self, token: Option<String>) {
self.session_token = token;
}
}
impl From<&VerifyRouterData> for PaymentsAuthorizeData { impl From<&VerifyRouterData> for PaymentsAuthorizeData {
fn from(data: &VerifyRouterData) -> Self { fn from(data: &VerifyRouterData) -> Self {
Self { Self {

View File

@ -319,7 +319,7 @@ impl ConnectorData {
enums::Connector::Powertranz => Ok(Box::new(&connector::Powertranz)), enums::Connector::Powertranz => Ok(Box::new(&connector::Powertranz)),
enums::Connector::Rapyd => Ok(Box::new(&connector::Rapyd)), enums::Connector::Rapyd => Ok(Box::new(&connector::Rapyd)),
enums::Connector::Shift4 => Ok(Box::new(&connector::Shift4)), enums::Connector::Shift4 => Ok(Box::new(&connector::Shift4)),
//enums::Connector::Square => Ok(Box::new(&connector::Square)), it is added as template code for future usage enums::Connector::Square => Ok(Box::new(&connector::Square)),
enums::Connector::Stax => Ok(Box::new(&connector::Stax)), enums::Connector::Stax => Ok(Box::new(&connector::Stax)),
enums::Connector::Stripe => Ok(Box::new(&connector::Stripe)), enums::Connector::Stripe => Ok(Box::new(&connector::Stripe)),
enums::Connector::Wise => Ok(Box::new(&connector::Wise)), enums::Connector::Wise => Ok(Box::new(&connector::Wise)),

View File

@ -171,6 +171,7 @@ key1 = "transaction key"
[square] [square]
api_key="API Key" api_key="API Key"
key1 = "transaction key"
[helcim] [helcim]
api_key="API Key" api_key="API Key"

View File

@ -1,18 +1,24 @@
use std::{str::FromStr, time::Duration};
use masking::Secret; use masking::Secret;
use router::types::{self, api, storage::enums}; use router::types::{
self, api,
storage::{self, enums},
PaymentsResponseData,
};
use test_utils::connector_auth::ConnectorAuthentication; use test_utils::connector_auth::ConnectorAuthentication;
use crate::utils::{self, ConnectorActions}; use crate::utils::{self, get_connector_transaction_id, Connector, ConnectorActions};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct SquareTest; struct SquareTest;
impl ConnectorActions for SquareTest {} impl ConnectorActions for SquareTest {}
impl utils::Connector for SquareTest { impl Connector for SquareTest {
fn get_data(&self) -> types::api::ConnectorData { fn get_data(&self) -> types::api::ConnectorData {
use router::connector::Square; use router::connector::Square;
types::api::ConnectorData { types::api::ConnectorData {
connector: Box::new(&Square), connector: Box::new(&Square),
connector_name: types::Connector::DummyConnector1, connector_name: types::Connector::Square,
get_token: types::api::GetToken::Connector, get_token: types::api::GetToken::Connector,
} }
} }
@ -33,20 +39,60 @@ impl utils::Connector for SquareTest {
static CONNECTOR: SquareTest = SquareTest {}; static CONNECTOR: SquareTest = SquareTest {};
fn get_default_payment_info() -> Option<utils::PaymentInfo> { fn get_default_payment_info(payment_method_token: Option<String>) -> Option<utils::PaymentInfo> {
None Some(utils::PaymentInfo {
address: None,
auth_type: None,
access_token: None,
connector_meta_data: None,
return_url: None,
connector_customer: None,
payment_method_token,
payout_method_data: None,
currency: None,
country: None,
})
} }
fn payment_method_details() -> Option<types::PaymentsAuthorizeData> { fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
None None
} }
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("100".to_string()),
..utils::CCardType::default().0
}),
browser_info: None,
amount: None,
currency: storage::enums::Currency::USD,
})
}
async fn create_token() -> Option<String> {
let token_response = CONNECTOR
.create_connector_pm_token(token_details(), get_default_payment_info(None))
.await
.expect("Authorize payment response");
match token_response.response.unwrap() {
PaymentsResponseData::TokenizationResponse { token } => Some(token),
_ => None,
}
}
// Cards Positive Tests // Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS). // Creates a payment using the manual capture flow (Non 3DS).
#[actix_web::test] #[actix_web::test]
async fn should_only_authorize_payment() { async fn should_only_authorize_payment() {
let response = CONNECTOR let response = CONNECTOR
.authorize_payment(payment_method_details(), get_default_payment_info()) .authorize_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await .await
.expect("Authorize payment response"); .expect("Authorize payment response");
assert_eq!(response.status, enums::AttemptStatus::Authorized); assert_eq!(response.status, enums::AttemptStatus::Authorized);
@ -56,7 +102,11 @@ async fn should_only_authorize_payment() {
#[actix_web::test] #[actix_web::test]
async fn should_capture_authorized_payment() { async fn should_capture_authorized_payment() {
let response = CONNECTOR 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(create_token().await),
)
.await .await
.expect("Capture payment response"); .expect("Capture payment response");
assert_eq!(response.status, enums::AttemptStatus::Charged); assert_eq!(response.status, enums::AttemptStatus::Charged);
@ -72,7 +122,7 @@ async fn should_partially_capture_authorized_payment() {
amount_to_capture: 50, amount_to_capture: 50,
..utils::PaymentCaptureType::default().0 ..utils::PaymentCaptureType::default().0
}), }),
get_default_payment_info(), get_default_payment_info(create_token().await),
) )
.await .await
.expect("Capture payment response"); .expect("Capture payment response");
@ -83,10 +133,13 @@ async fn should_partially_capture_authorized_payment() {
#[actix_web::test] #[actix_web::test]
async fn should_sync_authorized_payment() { async fn should_sync_authorized_payment() {
let authorize_response = CONNECTOR let authorize_response = CONNECTOR
.authorize_payment(payment_method_details(), get_default_payment_info()) .authorize_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await .await
.expect("Authorize payment response"); .expect("Authorize payment response");
let txn_id = utils::get_connector_transaction_id(authorize_response.response); let txn_id = get_connector_transaction_id(authorize_response.response);
let response = CONNECTOR let response = CONNECTOR
.psync_retry_till_status_matches( .psync_retry_till_status_matches(
enums::AttemptStatus::Authorized, enums::AttemptStatus::Authorized,
@ -96,7 +149,7 @@ async fn should_sync_authorized_payment() {
), ),
..Default::default() ..Default::default()
}), }),
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.expect("PSync response"); .expect("PSync response");
@ -114,7 +167,7 @@ async fn should_void_authorized_payment() {
cancellation_reason: Some("requested_by_customer".to_string()), cancellation_reason: Some("requested_by_customer".to_string()),
..Default::default() ..Default::default()
}), }),
get_default_payment_info(), get_default_payment_info(create_token().await),
) )
.await .await
.expect("Void payment response"); .expect("Void payment response");
@ -124,12 +177,21 @@ async fn should_void_authorized_payment() {
// Refunds a payment using the manual capture flow (Non 3DS). // Refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test] #[actix_web::test]
async fn should_refund_manually_captured_payment() { async fn should_refund_manually_captured_payment() {
let response = CONNECTOR let refund_response = CONNECTOR
.capture_payment_and_refund( .capture_payment_and_refund(
payment_method_details(), payment_method_details(),
None, None,
None, None,
get_default_payment_info(), get_default_payment_info(create_token().await),
)
.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(None),
) )
.await .await
.unwrap(); .unwrap();
@ -142,7 +204,7 @@ async fn should_refund_manually_captured_payment() {
// Partially refunds a payment using the manual capture flow (Non 3DS). // Partially refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test] #[actix_web::test]
async fn should_partially_refund_manually_captured_payment() { async fn should_partially_refund_manually_captured_payment() {
let response = CONNECTOR let refund_response = CONNECTOR
.capture_payment_and_refund( .capture_payment_and_refund(
payment_method_details(), payment_method_details(),
None, None,
@ -150,7 +212,16 @@ async fn should_partially_refund_manually_captured_payment() {
refund_amount: 50, refund_amount: 50,
..utils::PaymentRefundType::default().0 ..utils::PaymentRefundType::default().0
}), }),
get_default_payment_info(), get_default_payment_info(create_token().await),
)
.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(None),
) )
.await .await
.unwrap(); .unwrap();
@ -168,7 +239,7 @@ async fn should_sync_manually_captured_refund() {
payment_method_details(), payment_method_details(),
None, None,
None, None,
get_default_payment_info(), get_default_payment_info(create_token().await),
) )
.await .await
.unwrap(); .unwrap();
@ -177,7 +248,7 @@ async fn should_sync_manually_captured_refund() {
enums::RefundStatus::Success, enums::RefundStatus::Success,
refund_response.response.unwrap().connector_refund_id, refund_response.response.unwrap().connector_refund_id,
None, None,
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.unwrap(); .unwrap();
@ -191,7 +262,10 @@ async fn should_sync_manually_captured_refund() {
#[actix_web::test] #[actix_web::test]
async fn should_make_payment() { async fn should_make_payment() {
let authorize_response = CONNECTOR let authorize_response = CONNECTOR
.make_payment(payment_method_details(), get_default_payment_info()) .make_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await .await
.unwrap(); .unwrap();
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
@ -201,11 +275,14 @@ async fn should_make_payment() {
#[actix_web::test] #[actix_web::test]
async fn should_sync_auto_captured_payment() { async fn should_sync_auto_captured_payment() {
let authorize_response = CONNECTOR let authorize_response = CONNECTOR
.make_payment(payment_method_details(), get_default_payment_info()) .make_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await .await
.unwrap(); .unwrap();
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
let txn_id = utils::get_connector_transaction_id(authorize_response.response); let txn_id = get_connector_transaction_id(authorize_response.response);
assert_ne!(txn_id, None, "Empty connector transaction id"); assert_ne!(txn_id, None, "Empty connector transaction id");
let response = CONNECTOR let response = CONNECTOR
.psync_retry_till_status_matches( .psync_retry_till_status_matches(
@ -217,7 +294,7 @@ async fn should_sync_auto_captured_payment() {
capture_method: Some(enums::CaptureMethod::Automatic), capture_method: Some(enums::CaptureMethod::Automatic),
..Default::default() ..Default::default()
}), }),
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.unwrap(); .unwrap();
@ -227,8 +304,21 @@ async fn should_sync_auto_captured_payment() {
// Refunds a payment using the automatic capture flow (Non 3DS). // Refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test] #[actix_web::test]
async fn should_refund_auto_captured_payment() { async fn should_refund_auto_captured_payment() {
let refund_response = CONNECTOR
.make_payment_and_refund(
payment_method_details(),
None,
get_default_payment_info(create_token().await),
)
.await
.unwrap();
let response = CONNECTOR let response = CONNECTOR
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) .rsync_retry_till_status_matches(
enums::RefundStatus::Success,
refund_response.response.unwrap().connector_refund_id,
None,
get_default_payment_info(None),
)
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -247,44 +337,85 @@ async fn should_partially_refund_succeeded_payment() {
refund_amount: 50, refund_amount: 50,
..utils::PaymentRefundType::default().0 ..utils::PaymentRefundType::default().0
}), }),
get_default_payment_info(), get_default_payment_info(create_token().await),
) )
.await .await
.unwrap(); .unwrap();
assert_eq!(
refund_response.response.unwrap().refund_status,
enums::RefundStatus::Success,
);
}
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
CONNECTOR
.make_payment_and_multiple_refund(
payment_method_details(),
Some(types::RefundsData {
refund_amount: 50,
..utils::PaymentRefundType::default().0
}),
get_default_payment_info(),
)
.await;
}
// Synchronizes a refund using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_refund() {
let refund_response = CONNECTOR
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
.await
.unwrap();
let response = CONNECTOR let response = CONNECTOR
.rsync_retry_till_status_matches( .rsync_retry_till_status_matches(
enums::RefundStatus::Success, enums::RefundStatus::Success,
refund_response.response.unwrap().connector_refund_id, refund_response.response.unwrap().connector_refund_id,
None, None,
get_default_payment_info(), get_default_payment_info(None),
)
.await
.unwrap();
assert_eq!(
response.response.unwrap().refund_status,
enums::RefundStatus::Success,
);
}
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
//make a successful payment
let response = CONNECTOR
.make_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await
.unwrap();
let refund_data = Some(types::RefundsData {
refund_amount: 50,
..utils::PaymentRefundType::default().0
});
//try refund for previous payment
let transaction_id = get_connector_transaction_id(response.response).unwrap();
for _x in 0..2 {
tokio::time::sleep(Duration::from_secs(CONNECTOR.get_request_interval())).await; // to avoid 404 error
let refund_response = CONNECTOR
.refund_payment(
transaction_id.clone(),
refund_data.clone(),
get_default_payment_info(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(None),
)
.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(
payment_method_details(),
None,
get_default_payment_info(create_token().await),
)
.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(None),
) )
.await .await
.unwrap(); .unwrap();
@ -298,66 +429,93 @@ async fn should_sync_refund() {
// Creates a payment with incorrect CVC. // Creates a payment with incorrect CVC.
#[actix_web::test] #[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() { async fn should_fail_payment_for_incorrect_cvc() {
let response = CONNECTOR let token_response = CONNECTOR
.make_payment( .create_connector_pm_token(
Some(types::PaymentsAuthorizeData { Some(types::PaymentMethodTokenizationData {
payment_method_data: types::api::PaymentMethodData::Card(api::Card { 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("".to_string()),
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
..utils::PaymentAuthorizeType::default().0 browser_info: None,
amount: None,
currency: storage::enums::Currency::USD,
}), }),
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.unwrap(); .expect("Authorize payment response");
assert_eq!( assert_eq!(
response.response.unwrap_err().message, token_response
"Your card's security code is invalid.".to_string(), .response
.unwrap_err()
.reason
.unwrap_or("".to_string()),
"Missing required parameter.".to_string(),
); );
} }
// Creates a payment with incorrect expiry month. // Creates a payment with incorrect expiry month.
#[actix_web::test] #[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() { async fn should_fail_payment_for_invalid_exp_month() {
let response = CONNECTOR let token_response = CONNECTOR
.make_payment( .create_connector_pm_token(
Some(types::PaymentsAuthorizeData { Some(types::PaymentMethodTokenizationData {
payment_method_data: types::api::PaymentMethodData::Card(api::Card { 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_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::CCardType::default().0
}), }),
..utils::PaymentAuthorizeType::default().0 browser_info: None,
amount: None,
currency: storage::enums::Currency::USD,
}), }),
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.unwrap(); .expect("Authorize payment response");
assert_eq!( assert_eq!(
response.response.unwrap_err().message, token_response
"Your card's expiration month is invalid.".to_string(), .response
.unwrap_err()
.reason
.unwrap_or("".to_string()),
"Invalid card expiration date.".to_string(),
); );
} }
// Creates a payment with incorrect expiry year. // Creates a payment with incorrect expiry year.
#[actix_web::test] #[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() { async fn should_fail_payment_for_incorrect_expiry_year() {
let response = CONNECTOR let token_response = CONNECTOR
.make_payment( .create_connector_pm_token(
Some(types::PaymentsAuthorizeData { Some(types::PaymentMethodTokenizationData {
payment_method_data: types::api::PaymentMethodData::Card(api::Card { payment_method_data: types::api::PaymentMethodData::Card(api::Card {
card_number: cards::CardNumber::from_str("4111111111111111").unwrap(),
card_exp_month: Secret::new("11".to_string()),
card_exp_year: Secret::new("2000".to_string()), card_exp_year: Secret::new("2000".to_string()),
card_cvc: Secret::new("123".to_string()),
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
..utils::PaymentAuthorizeType::default().0 browser_info: None,
amount: None,
currency: storage::enums::Currency::USD,
}), }),
get_default_payment_info(), get_default_payment_info(None),
) )
.await .await
.unwrap(); .expect("Authorize payment response");
assert_eq!( assert_eq!(
response.response.unwrap_err().message, token_response
"Your card's expiration year is invalid.".to_string(), .response
.unwrap_err()
.reason
.unwrap_or("".to_string()),
"Invalid card expiration date.".to_string(),
); );
} }
@ -365,19 +523,27 @@ async fn should_fail_payment_for_incorrect_expiry_year() {
#[actix_web::test] #[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() { async fn should_fail_void_payment_for_auto_capture() {
let authorize_response = CONNECTOR let authorize_response = CONNECTOR
.make_payment(payment_method_details(), get_default_payment_info()) .make_payment(
payment_method_details(),
get_default_payment_info(create_token().await),
)
.await .await
.unwrap(); .unwrap();
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
let txn_id = utils::get_connector_transaction_id(authorize_response.response); let txn_id = get_connector_transaction_id(authorize_response.response);
assert_ne!(txn_id, None, "Empty connector transaction id"); assert_ne!(txn_id, None, "Empty connector transaction id");
let void_response = CONNECTOR let void_response = CONNECTOR
.void_payment(txn_id.unwrap(), None, get_default_payment_info()) .void_payment(
txn_id.clone().unwrap(),
None,
get_default_payment_info(None),
)
.await .await
.unwrap(); .unwrap();
let connector_transaction_id = txn_id.unwrap();
assert_eq!( assert_eq!(
void_response.response.unwrap_err().message, void_response.response.unwrap_err().reason.unwrap_or("".to_string()),
"You cannot cancel this PaymentIntent because it has a status of succeeded." format!("Payment {connector_transaction_id} is in inflight state COMPLETED, which is invalid for the requested operation")
); );
} }
@ -385,12 +551,20 @@ async fn should_fail_void_payment_for_auto_capture() {
#[actix_web::test] #[actix_web::test]
async fn should_fail_capture_for_invalid_payment() { async fn should_fail_capture_for_invalid_payment() {
let capture_response = CONNECTOR let capture_response = CONNECTOR
.capture_payment("123456789".to_string(), None, get_default_payment_info()) .capture_payment(
"123456789".to_string(),
None,
get_default_payment_info(create_token().await),
)
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
capture_response.response.unwrap_err().message, capture_response
String::from("No such payment_intent: '123456789'") .response
.unwrap_err()
.reason
.unwrap_or("".to_string()),
String::from("Could not find payment with id: 123456789")
); );
} }
@ -404,13 +578,17 @@ async fn should_fail_for_refund_amount_higher_than_payment_amount() {
refund_amount: 150, refund_amount: 150,
..utils::PaymentRefundType::default().0 ..utils::PaymentRefundType::default().0
}), }),
get_default_payment_info(), get_default_payment_info(create_token().await),
) )
.await .await
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
response.response.unwrap_err().message, response
"Refund amount (₹1.50) is greater than charge amount (₹1.00)", .response
.unwrap_err()
.reason
.unwrap_or("".to_string()),
"The requested refund amount exceeds the amount available to refund.",
); );
} }

View File

@ -69,6 +69,8 @@ fn token_details() -> Option<types::PaymentMethodTokenizationData> {
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
browser_info: None, browser_info: None,
amount: None,
currency: enums::Currency::USD,
}) })
} }
@ -477,6 +479,8 @@ async fn should_fail_payment_for_incorrect_cvc() {
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
browser_info: None, browser_info: None,
amount: None,
currency: enums::Currency::USD,
}), }),
get_default_payment_info(connector_customer_id, None), get_default_payment_info(connector_customer_id, None),
) )
@ -513,6 +517,8 @@ async fn should_fail_payment_for_invalid_exp_month() {
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
browser_info: None, browser_info: None,
amount: None,
currency: enums::Currency::USD,
}), }),
get_default_payment_info(connector_customer_id, None), get_default_payment_info(connector_customer_id, None),
) )
@ -549,6 +555,8 @@ async fn should_fail_payment_for_incorrect_expiry_year() {
..utils::CCardType::default().0 ..utils::CCardType::default().0
}), }),
browser_info: None, browser_info: None,
amount: None,
currency: enums::Currency::USD,
}), }),
get_default_payment_info(connector_customer_id, None), get_default_payment_info(connector_customer_id, None),
) )

View File

@ -963,6 +963,8 @@ impl Default for TokenType {
let data = types::PaymentMethodTokenizationData { let data = types::PaymentMethodTokenizationData {
payment_method_data: types::api::PaymentMethodData::Card(CCardType::default().0), payment_method_data: types::api::PaymentMethodData::Card(CCardType::default().0),
browser_info: None, browser_info: None,
amount: Some(100),
currency: enums::Currency::USD,
}; };
Self(data) Self(data)
} }

View File

@ -49,7 +49,7 @@ pub struct ConnectorAuthentication {
pub powertranz: Option<BodyKey>, pub powertranz: Option<BodyKey>,
pub rapyd: Option<BodyKey>, pub rapyd: Option<BodyKey>,
pub shift4: Option<HeaderKey>, pub shift4: Option<HeaderKey>,
pub square: Option<HeaderKey>, pub square: Option<BodyKey>,
pub stax: Option<HeaderKey>, pub stax: Option<HeaderKey>,
pub stripe: Option<HeaderKey>, pub stripe: Option<HeaderKey>,
pub stripe_au: Option<HeaderKey>, pub stripe_au: Option<HeaderKey>,

View File

@ -100,6 +100,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/"
rapyd.base_url = "https://sandboxapi.rapyd.net" rapyd.base_url = "https://sandboxapi.rapyd.net"
shift4.base_url = "https://api.shift4.com/" shift4.base_url = "https://api.shift4.com/"
square.base_url = "https://connect.squareupsandbox.com/" square.base_url = "https://connect.squareupsandbox.com/"
square.secondary_base_url = "https://pci-connect.squareupsandbox.com/"
stax.base_url = "https://apiprod.fattlabs.com/" stax.base_url = "https://apiprod.fattlabs.com/"
stripe.base_url = "https://api.stripe.com/" stripe.base_url = "https://api.stripe.com/"
stripe.base_url_file_upload = "https://files.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/"

View File

@ -3858,6 +3858,7 @@
"powertranz", "powertranz",
"rapyd", "rapyd",
"shift4", "shift4",
"square",
"stax", "stax",
"stripe", "stripe",
"trustpay", "trustpay",