mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 12:06:56 +08:00
feat(connector): [Shift4] add support for card 3DS payment (#828)
This commit is contained in:
@ -513,13 +513,13 @@ impl
|
|||||||
Ok(Some(
|
Ok(Some(
|
||||||
services::RequestBuilder::new()
|
services::RequestBuilder::new()
|
||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsComeplteAuthorizeType::get_url(
|
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.headers(types::PaymentsComeplteAuthorizeType::get_headers(
|
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.body(types::PaymentsComeplteAuthorizeType::get_request_body(
|
.body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||||
self, req,
|
self, req,
|
||||||
)?)
|
)?)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@ -267,7 +267,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
|||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
http_code: res.status_code,
|
||||||
},
|
},
|
||||||
get_payment_flow(data.request.is_auto_capture()),
|
get_payment_flow(data.request.is_auto_capture()?),
|
||||||
))
|
))
|
||||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
}
|
}
|
||||||
@ -440,7 +440,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
|||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
http_code: res.status_code,
|
||||||
},
|
},
|
||||||
get_payment_flow(data.request.is_auto_capture()),
|
get_payment_flow(data.request.is_auto_capture()?),
|
||||||
))
|
))
|
||||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,7 +64,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BamboraPaymentsRequest {
|
|||||||
expiry_year: req_card.card_exp_year,
|
expiry_year: req_card.card_exp_year,
|
||||||
cvd: req_card.card_cvc,
|
cvd: req_card.card_cvc,
|
||||||
three_d_secure: three_ds,
|
three_d_secure: three_ds,
|
||||||
complete: item.request.is_auto_capture(),
|
complete: item.request.is_auto_capture()?,
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
amount: item.request.amount,
|
amount: item.request.amount,
|
||||||
|
|||||||
@ -34,7 +34,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for FortePaymentsRequest {
|
|||||||
expiry_month: req_card.card_exp_month,
|
expiry_month: req_card.card_exp_month,
|
||||||
expiry_year: req_card.card_exp_year,
|
expiry_year: req_card.card_exp_year,
|
||||||
cvc: req_card.card_cvc,
|
cvc: req_card.card_cvc,
|
||||||
complete: item.request.is_auto_capture(),
|
complete: item.request.is_auto_capture()?,
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
amount: item.request.amount,
|
amount: item.request.amount,
|
||||||
|
|||||||
@ -152,14 +152,14 @@ impl
|
|||||||
Ok(Some(
|
Ok(Some(
|
||||||
services::RequestBuilder::new()
|
services::RequestBuilder::new()
|
||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsComeplteAuthorizeType::get_url(
|
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.attach_default_headers()
|
.attach_default_headers()
|
||||||
.headers(types::PaymentsComeplteAuthorizeType::get_headers(
|
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.body(types::PaymentsComeplteAuthorizeType::get_request_body(
|
.body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||||
self, req,
|
self, req,
|
||||||
)?)
|
)?)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@ -148,14 +148,14 @@ impl
|
|||||||
Ok(Some(
|
Ok(Some(
|
||||||
services::RequestBuilder::new()
|
services::RequestBuilder::new()
|
||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsComeplteAuthorizeType::get_url(
|
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.attach_default_headers()
|
.attach_default_headers()
|
||||||
.headers(types::PaymentsComeplteAuthorizeType::get_headers(
|
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.body(types::PaymentsComeplteAuthorizeType::get_request_body(
|
.body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||||
self, req,
|
self, req,
|
||||||
)?)
|
)?)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@ -80,7 +80,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaypalPaymentsRequest {
|
|||||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
match item.request.payment_method_data {
|
match item.request.payment_method_data {
|
||||||
api_models::payments::PaymentMethodData::Card(ref ccard) => {
|
api_models::payments::PaymentMethodData::Card(ref ccard) => {
|
||||||
let intent = match item.request.is_auto_capture() {
|
let intent = match item.request.is_auto_capture()? {
|
||||||
true => PaypalPaymentIntent::Capture,
|
true => PaypalPaymentIntent::Capture,
|
||||||
false => PaypalPaymentIntent::Authorize,
|
false => PaypalPaymentIntent::Authorize,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,9 +10,12 @@ use super::utils::RefundsRequestData;
|
|||||||
use crate::{
|
use crate::{
|
||||||
configs::settings,
|
configs::settings,
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, CustomResult},
|
core::{
|
||||||
headers,
|
errors::{self, CustomResult},
|
||||||
services::{self, ConnectorIntegration},
|
payments,
|
||||||
|
},
|
||||||
|
headers, routes,
|
||||||
|
services::{self, request, ConnectorIntegration},
|
||||||
types::{
|
types::{
|
||||||
self,
|
self,
|
||||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||||
@ -93,7 +96,15 @@ impl ConnectorCommon for Shift4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl api::Payment for Shift4 {}
|
impl api::Payment for Shift4 {}
|
||||||
|
impl api::PaymentVoid for Shift4 {}
|
||||||
|
impl api::PaymentSync for Shift4 {}
|
||||||
|
impl api::PaymentCapture for Shift4 {}
|
||||||
|
impl api::PaymentSession for Shift4 {}
|
||||||
|
impl api::PaymentAuthorize for Shift4 {}
|
||||||
|
impl api::PaymentsCompleteAuthorize for Shift4 {}
|
||||||
|
impl api::Refund for Shift4 {}
|
||||||
|
impl api::RefundExecute for Shift4 {}
|
||||||
|
impl api::RefundSync for Shift4 {}
|
||||||
impl api::PaymentToken for Shift4 {}
|
impl api::PaymentToken for Shift4 {}
|
||||||
|
|
||||||
impl
|
impl
|
||||||
@ -120,14 +131,128 @@ impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::Payments
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentVoid for Shift4 {}
|
#[async_trait::async_trait]
|
||||||
|
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||||
|
for Shift4
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
_req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
Ok(format!("{}charges", self.base_url(connectors)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?;
|
||||||
|
let req =
|
||||||
|
utils::Encode::<shift4::Shift4PaymentsRequest>::encode_to_string_of_json(&req_obj)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_pretasks(
|
||||||
|
&self,
|
||||||
|
router_data: &mut types::PaymentsAuthorizeRouterData,
|
||||||
|
app_state: &routes::AppState,
|
||||||
|
) -> CustomResult<(), errors::ConnectorError> {
|
||||||
|
match router_data.auth_type {
|
||||||
|
storage_models::enums::AuthenticationType::ThreeDs => {
|
||||||
|
let integ: Box<
|
||||||
|
&(dyn ConnectorIntegration<
|
||||||
|
api::InitPayment,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> + Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static),
|
||||||
|
> = Box::new(&Self);
|
||||||
|
let init_data = &types::PaymentsInitRouterData::from((
|
||||||
|
&router_data,
|
||||||
|
router_data.request.clone(),
|
||||||
|
));
|
||||||
|
let init_resp = services::execute_connector_processing_step(
|
||||||
|
app_state,
|
||||||
|
integ,
|
||||||
|
init_data,
|
||||||
|
payments::CallConnectorAction::Trigger,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if init_resp.request.enrolled_for_3ds {
|
||||||
|
router_data.response = init_resp.response;
|
||||||
|
router_data.status = init_resp.status;
|
||||||
|
} else {
|
||||||
|
router_data.request.enrolled_for_3ds = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storage_models::enums::AuthenticationType::NoThreeDs => (),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||||
|
let response: shift4::Shift4NonThreeDsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Shift4NonThreeDsResponse")
|
||||||
|
.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: types::Response,
|
||||||
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||||
for Shift4
|
for Shift4
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentSync for Shift4 {}
|
|
||||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||||
for Shift4
|
for Shift4
|
||||||
{
|
{
|
||||||
@ -187,9 +312,9 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
|||||||
data: &types::PaymentsSyncRouterData,
|
data: &types::PaymentsSyncRouterData,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||||
let response: shift4::Shift4PaymentsResponse = res
|
let response: shift4::Shift4NonThreeDsResponse = res
|
||||||
.response
|
.response
|
||||||
.parse_struct("shift4 PaymentsResponse")
|
.parse_struct("Shift4NonThreeDsResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
types::RouterData::try_from(types::ResponseRouterData {
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
@ -200,8 +325,6 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentCapture for Shift4 {}
|
|
||||||
|
|
||||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||||
for Shift4
|
for Shift4
|
||||||
{
|
{
|
||||||
@ -239,9 +362,9 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
|||||||
data: &types::PaymentsCaptureRouterData,
|
data: &types::PaymentsCaptureRouterData,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||||
let response: shift4::Shift4PaymentsResponse = res
|
let response: shift4::Shift4NonThreeDsResponse = res
|
||||||
.response
|
.response
|
||||||
.parse_struct("Shift4PaymentsResponse")
|
.parse_struct("Shift4NonThreeDsResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
types::ResponseRouterData {
|
types::ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
@ -273,25 +396,37 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentSession for Shift4 {}
|
|
||||||
|
|
||||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||||
for Shift4
|
for Shift4
|
||||||
{
|
{
|
||||||
//TODO: implement sessions flow
|
//TODO: implement sessions flow
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentAuthorize for Shift4 {}
|
impl
|
||||||
|
ConnectorIntegration<
|
||||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
api::InitPayment,
|
||||||
for Shift4
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> for Shift4
|
||||||
{
|
{
|
||||||
fn get_headers(
|
fn get_headers(
|
||||||
&self,
|
&self,
|
||||||
req: &types::PaymentsAuthorizeRouterData,
|
req: &types::PaymentsInitRouterData,
|
||||||
connectors: &settings::Connectors,
|
_connectors: &settings::Connectors,
|
||||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
self.build_headers(req, connectors)
|
let mut headers = vec![
|
||||||
|
(
|
||||||
|
headers::CONTENT_TYPE.to_string(),
|
||||||
|
"application/x-www-form-urlencoded".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
headers::ACCEPT.to_string(),
|
||||||
|
self.common_get_content_type().to_string(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||||
|
headers.append(&mut api_key);
|
||||||
|
Ok(headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_content_type(&self) -> &'static str {
|
fn get_content_type(&self) -> &'static str {
|
||||||
@ -300,55 +435,54 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
|||||||
|
|
||||||
fn get_url(
|
fn get_url(
|
||||||
&self,
|
&self,
|
||||||
_req: &types::PaymentsAuthorizeRouterData,
|
_req: &types::PaymentsInitRouterData,
|
||||||
connectors: &settings::Connectors,
|
connectors: &settings::Connectors,
|
||||||
) -> CustomResult<String, errors::ConnectorError> {
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
Ok(format!("{}charges", self.base_url(connectors)))
|
Ok(format!("{}3d-secure", self.base_url(connectors)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_request_body(
|
fn get_request_body(
|
||||||
&self,
|
&self,
|
||||||
req: &types::PaymentsAuthorizeRouterData,
|
req: &types::PaymentsInitRouterData,
|
||||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
let shift4_req = utils::Encode::<shift4::Shift4PaymentsRequest>::convert_and_encode(req)
|
let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?;
|
||||||
|
let req =
|
||||||
|
utils::Encode::<shift4::Shift4PaymentsRequest>::encode_to_string_of_json(&req_obj)
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
Ok(Some(shift4_req))
|
Ok(Some(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_request(
|
fn build_request(
|
||||||
&self,
|
&self,
|
||||||
req: &types::PaymentsAuthorizeRouterData,
|
req: &types::PaymentsInitRouterData,
|
||||||
connectors: &settings::Connectors,
|
connectors: &settings::Connectors,
|
||||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
services::RequestBuilder::new()
|
services::RequestBuilder::new()
|
||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsAuthorizeType::get_url(
|
.url(&types::PaymentsInitType::get_url(self, req, connectors)?)
|
||||||
self, req, connectors,
|
.content_type(request::ContentType::FormUrlEncoded)
|
||||||
)?)
|
|
||||||
.attach_default_headers()
|
.attach_default_headers()
|
||||||
.headers(types::PaymentsAuthorizeType::get_headers(
|
.headers(types::PaymentsInitType::get_headers(self, req, connectors)?)
|
||||||
self, req, connectors,
|
.body(types::PaymentsInitType::get_request_body(self, req)?)
|
||||||
)?)
|
|
||||||
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
|
|
||||||
.build(),
|
.build(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_response(
|
fn handle_response(
|
||||||
&self,
|
&self,
|
||||||
data: &types::PaymentsAuthorizeRouterData,
|
data: &types::PaymentsInitRouterData,
|
||||||
res: types::Response,
|
res: types::Response,
|
||||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
) -> CustomResult<types::PaymentsInitRouterData, errors::ConnectorError> {
|
||||||
let response: shift4::Shift4PaymentsResponse = res
|
let response: shift4::Shift4ThreeDsResponse = res
|
||||||
.response
|
.response
|
||||||
.parse_struct("Shift4PaymentsResponse")
|
.parse_struct("Shift4ThreeDsResponse")
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
types::ResponseRouterData {
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
response,
|
response,
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
http_code: res.status_code,
|
http_code: res.status_code,
|
||||||
}
|
})
|
||||||
.try_into()
|
|
||||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,9 +494,89 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::Refund for Shift4 {}
|
impl
|
||||||
impl api::RefundExecute for Shift4 {}
|
ConnectorIntegration<
|
||||||
impl api::RefundSync for Shift4 {}
|
api::CompleteAuthorize,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> for Shift4
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
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::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
Ok(format!("{}charges", self.base_url(connectors)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?;
|
||||||
|
let req =
|
||||||
|
utils::Encode::<shift4::Shift4PaymentsRequest>::encode_to_string_of_json(&req_obj)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||||
|
self, req,
|
||||||
|
)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
|
||||||
|
let response: shift4::Shift4NonThreeDsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Shift4NonThreeDsResponse")
|
||||||
|
.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: types::Response,
|
||||||
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Shift4 {
|
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Shift4 {
|
||||||
fn get_headers(
|
fn get_headers(
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
use api_models::payments;
|
use api_models::payments;
|
||||||
|
use common_utils::pii::SecretSerdeValue;
|
||||||
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
connector::utils::{
|
||||||
|
to_connector_meta, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
|
||||||
|
RouterData,
|
||||||
|
},
|
||||||
core::errors,
|
core::errors,
|
||||||
pii, services,
|
pii, services,
|
||||||
types::{self, api, storage::enums, transformers::ForeignFrom},
|
types::{self, api, storage::enums, transformers::ForeignFrom},
|
||||||
@ -11,11 +17,33 @@ use crate::{
|
|||||||
|
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Shift4PaymentsRequest {
|
||||||
|
Non3DSRequest(Box<Shift4Non3DSRequest>),
|
||||||
|
ThreeDSRequest(Box<Shift43DSRequest>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Shift4PaymentsRequest {
|
pub struct Shift43DSRequest {
|
||||||
amount: String,
|
amount: String,
|
||||||
card: Option<Card>,
|
currency: String,
|
||||||
|
#[serde(rename = "card[number]")]
|
||||||
|
pub card_number: Secret<String, common_utils::pii::CardNumber>,
|
||||||
|
#[serde(rename = "card[expMonth]")]
|
||||||
|
pub card_exp_month: Secret<String>,
|
||||||
|
#[serde(rename = "card[expYear]")]
|
||||||
|
pub card_exp_year: Secret<String>,
|
||||||
|
return_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_with::skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Shift4Non3DSRequest {
|
||||||
|
amount: String,
|
||||||
|
card: Option<CardPayment>,
|
||||||
currency: String,
|
currency: String,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
payment_method: Option<PaymentMethod>,
|
payment_method: Option<PaymentMethod>,
|
||||||
@ -68,15 +96,26 @@ pub struct DeviceData;
|
|||||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Card {
|
pub struct Card {
|
||||||
number: Secret<String, common_utils::pii::CardNumber>,
|
pub number: Secret<String, common_utils::pii::CardNumber>,
|
||||||
exp_month: Secret<String>,
|
pub exp_month: Secret<String>,
|
||||||
exp_year: Secret<String>,
|
pub exp_year: Secret<String>,
|
||||||
cardholder_name: Secret<String>,
|
pub cardholder_name: Secret<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for Shift4PaymentsRequest {
|
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CardPayment {
|
||||||
|
RawCard(Box<Card>),
|
||||||
|
CardToken(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TryFrom<&types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>>
|
||||||
|
for Shift4PaymentsRequest
|
||||||
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
fn try_from(
|
||||||
|
item: &types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
match &item.request.payment_method_data {
|
match &item.request.payment_method_data {
|
||||||
api::PaymentMethodData::Card(ccard) => get_card_payment_request(item, ccard),
|
api::PaymentMethodData::Card(ccard) => get_card_payment_request(item, ccard),
|
||||||
api::PaymentMethodData::BankRedirect(redirect_data) => {
|
api::PaymentMethodData::BankRedirect(redirect_data) => {
|
||||||
@ -87,33 +126,78 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for Shift4PaymentsRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_card_payment_request(
|
impl<T> TryFrom<&types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>>
|
||||||
item: &types::PaymentsAuthorizeRouterData,
|
for Shift4PaymentsRequest
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(
|
||||||
|
item: &types::RouterData<T, types::CompleteAuthorizeData, types::PaymentsResponseData>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
match &item.request.payment_method_data {
|
||||||
|
Some(api::PaymentMethodData::Card(_)) => {
|
||||||
|
let card_token: Shift4CardToken =
|
||||||
|
to_connector_meta(item.request.connector_meta.clone())?;
|
||||||
|
Ok(Self::Non3DSRequest(Box::new(Shift4Non3DSRequest {
|
||||||
|
amount: item.request.amount.to_string(),
|
||||||
|
card: Some(CardPayment::CardToken(card_token.id)),
|
||||||
|
currency: item.request.currency.to_string(),
|
||||||
|
description: item.description.clone(),
|
||||||
|
captured: item.request.is_auto_capture()?,
|
||||||
|
payment_method: None,
|
||||||
|
flow: None,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
_ => Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_card_payment_request<T>(
|
||||||
|
item: &types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||||
card: &api_models::payments::Card,
|
card: &api_models::payments::Card,
|
||||||
) -> Result<Shift4PaymentsRequest, Error> {
|
) -> Result<Shift4PaymentsRequest, Error> {
|
||||||
let submit_for_settlement = submit_for_settlement(item);
|
let submit_for_settlement = item.request.is_auto_capture()?;
|
||||||
let card = Some(Card {
|
let card = Card {
|
||||||
number: card.card_number.clone(),
|
number: card.card_number.clone(),
|
||||||
exp_month: card.card_exp_month.clone(),
|
exp_month: card.card_exp_month.clone(),
|
||||||
exp_year: card.card_exp_year.clone(),
|
exp_year: card.card_exp_year.clone(),
|
||||||
cardholder_name: card.card_holder_name.clone(),
|
cardholder_name: card.card_holder_name.clone(),
|
||||||
});
|
};
|
||||||
Ok(Shift4PaymentsRequest {
|
if item.is_three_ds() {
|
||||||
|
Ok(Shift4PaymentsRequest::ThreeDSRequest(Box::new(
|
||||||
|
Shift43DSRequest {
|
||||||
amount: item.request.amount.to_string(),
|
amount: item.request.amount.to_string(),
|
||||||
card,
|
currency: item.request.currency.to_string(),
|
||||||
|
card_number: card.number,
|
||||||
|
card_exp_month: card.exp_month,
|
||||||
|
card_exp_year: card.exp_year,
|
||||||
|
return_url: item
|
||||||
|
.request
|
||||||
|
.complete_authorize_url
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| errors::ConnectorError::RequestEncodingFailed)?,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(Shift4PaymentsRequest::Non3DSRequest(Box::new(
|
||||||
|
Shift4Non3DSRequest {
|
||||||
|
amount: item.request.amount.to_string(),
|
||||||
|
card: Some(CardPayment::RawCard(Box::new(card))),
|
||||||
currency: item.request.currency.to_string(),
|
currency: item.request.currency.to_string(),
|
||||||
description: item.description.clone(),
|
description: item.description.clone(),
|
||||||
captured: submit_for_settlement,
|
captured: submit_for_settlement,
|
||||||
payment_method: None,
|
payment_method: None,
|
||||||
flow: None,
|
flow: None,
|
||||||
})
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bank_redirect_request(
|
fn get_bank_redirect_request<T>(
|
||||||
item: &types::PaymentsAuthorizeRouterData,
|
item: &types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||||
redirect_data: &payments::BankRedirectData,
|
redirect_data: &payments::BankRedirectData,
|
||||||
) -> Result<Shift4PaymentsRequest, Error> {
|
) -> Result<Shift4PaymentsRequest, Error> {
|
||||||
let submit_for_settlement = submit_for_settlement(item);
|
let submit_for_settlement = item.request.is_auto_capture()?;
|
||||||
let method_type = PaymentMethodType::from(redirect_data);
|
let method_type = PaymentMethodType::from(redirect_data);
|
||||||
let billing = get_billing(item)?;
|
let billing = get_billing(item)?;
|
||||||
let payment_method = Some(PaymentMethod {
|
let payment_method = Some(PaymentMethod {
|
||||||
@ -121,7 +205,8 @@ fn get_bank_redirect_request(
|
|||||||
billing,
|
billing,
|
||||||
});
|
});
|
||||||
let flow = get_flow(item);
|
let flow = get_flow(item);
|
||||||
Ok(Shift4PaymentsRequest {
|
Ok(Shift4PaymentsRequest::Non3DSRequest(Box::new(
|
||||||
|
Shift4Non3DSRequest {
|
||||||
amount: item.request.amount.to_string(),
|
amount: item.request.amount.to_string(),
|
||||||
card: None,
|
card: None,
|
||||||
currency: item.request.currency.to_string(),
|
currency: item.request.currency.to_string(),
|
||||||
@ -129,7 +214,8 @@ fn get_bank_redirect_request(
|
|||||||
captured: submit_for_settlement,
|
captured: submit_for_settlement,
|
||||||
payment_method,
|
payment_method,
|
||||||
flow: Some(flow),
|
flow: Some(flow),
|
||||||
})
|
},
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&payments::BankRedirectData> for PaymentMethodType {
|
impl From<&payments::BankRedirectData> for PaymentMethodType {
|
||||||
@ -143,13 +229,17 @@ impl From<&payments::BankRedirectData> for PaymentMethodType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_flow(item: &types::PaymentsAuthorizeRouterData) -> Flow {
|
fn get_flow<T>(
|
||||||
|
item: &types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||||
|
) -> Flow {
|
||||||
Flow {
|
Flow {
|
||||||
return_url: item.request.router_return_url.clone(),
|
return_url: item.request.router_return_url.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_billing(item: &types::PaymentsAuthorizeRouterData) -> Result<Option<Billing>, Error> {
|
fn get_billing<T>(
|
||||||
|
item: &types::RouterData<T, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
|
||||||
|
) -> Result<Option<Billing>, Error> {
|
||||||
let billing_address = item
|
let billing_address = item
|
||||||
.address
|
.address
|
||||||
.billing
|
.billing
|
||||||
@ -176,13 +266,6 @@ fn get_address_details(address_details: Option<&payments::AddressDetails>) -> Op
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_for_settlement(item: &types::PaymentsAuthorizeRouterData) -> bool {
|
|
||||||
matches!(
|
|
||||||
item.request.capture_method,
|
|
||||||
Some(enums::CaptureMethod::Automatic) | None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth Struct
|
// Auth Struct
|
||||||
pub struct Shift4AuthType {
|
pub struct Shift4AuthType {
|
||||||
pub(super) api_key: String,
|
pub(super) api_key: String,
|
||||||
@ -257,8 +340,7 @@ pub struct Shift4WebhookObjectResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
pub struct Shift4NonThreeDsResponse {
|
||||||
pub struct Shift4PaymentsResponse {
|
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub currency: String,
|
pub currency: String,
|
||||||
pub amount: u32,
|
pub amount: u32,
|
||||||
@ -268,6 +350,45 @@ pub struct Shift4PaymentsResponse {
|
|||||||
pub flow: Option<FlowResponse>,
|
pub flow: Option<FlowResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize)]
|
||||||
|
pub struct Shift4ThreeDsResponse {
|
||||||
|
pub enrolled: bool,
|
||||||
|
pub version: Option<String>,
|
||||||
|
#[serde(rename = "redirectUrl")]
|
||||||
|
pub redirect_url: Option<Url>,
|
||||||
|
pub token: Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize)]
|
||||||
|
pub struct Token {
|
||||||
|
pub id: String,
|
||||||
|
pub created: i64,
|
||||||
|
#[serde(rename = "objectType")]
|
||||||
|
pub object_type: String,
|
||||||
|
pub first6: String,
|
||||||
|
pub last4: String,
|
||||||
|
pub fingerprint: Secret<String>,
|
||||||
|
pub brand: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub token_type: String,
|
||||||
|
pub country: String,
|
||||||
|
pub used: bool,
|
||||||
|
#[serde(rename = "threeDSecureInfo")]
|
||||||
|
pub three_d_secure_info: ThreeDSecureInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Deserialize)]
|
||||||
|
pub struct ThreeDSecureInfo {
|
||||||
|
pub amount: i64,
|
||||||
|
pub currency: String,
|
||||||
|
pub enrolled: bool,
|
||||||
|
#[serde(rename = "liabilityShift")]
|
||||||
|
pub liability_shift: Option<String>,
|
||||||
|
pub version: String,
|
||||||
|
#[serde(rename = "authenticationFlow")]
|
||||||
|
pub authentication_flow: Option<SecretSerdeValue>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FlowResponse {
|
pub struct FlowResponse {
|
||||||
@ -290,13 +411,73 @@ pub enum NextAction {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T>
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
TryFrom<types::ResponseRouterData<F, Shift4PaymentsResponse, T, types::PaymentsResponseData>>
|
pub struct Shift4CardToken {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F>
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
Shift4ThreeDsResponse,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(
|
||||||
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
Shift4ThreeDsResponse,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let redirection_data = item
|
||||||
|
.response
|
||||||
|
.redirect_url
|
||||||
|
.map(|url| services::RedirectForm::from((url, services::Method::Get)));
|
||||||
|
Ok(Self {
|
||||||
|
status: if redirection_data.is_some() {
|
||||||
|
enums::AttemptStatus::AuthenticationPending
|
||||||
|
} else {
|
||||||
|
enums::AttemptStatus::Pending
|
||||||
|
},
|
||||||
|
request: types::PaymentsAuthorizeData {
|
||||||
|
enrolled_for_3ds: item.response.enrolled,
|
||||||
|
..item.data.request
|
||||||
|
},
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::NoResponseId,
|
||||||
|
redirection_data,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: Some(
|
||||||
|
serde_json::to_value(Shift4CardToken {
|
||||||
|
id: item.response.token.id,
|
||||||
|
})
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F>
|
||||||
|
TryFrom<types::ResponseRouterData<F, Shift4NonThreeDsResponse, T, types::PaymentsResponseData>>
|
||||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::ResponseRouterData<F, Shift4PaymentsResponse, T, types::PaymentsResponseData>,
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
Shift4NonThreeDsResponse,
|
||||||
|
T,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status: enums::AttemptStatus::foreign_from((
|
status: enums::AttemptStatus::foreign_from((
|
||||||
|
|||||||
@ -142,7 +142,7 @@ impl<Flow, Request, Response> RouterData for types::RouterData<Flow, Request, Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait PaymentsAuthorizeRequestData {
|
pub trait PaymentsAuthorizeRequestData {
|
||||||
fn is_auto_capture(&self) -> bool;
|
fn is_auto_capture(&self) -> Result<bool, Error>;
|
||||||
fn get_email(&self) -> Result<Secret<String, Email>, Error>;
|
fn get_email(&self) -> Result<Secret<String, Email>, Error>;
|
||||||
fn get_browser_info(&self) -> Result<types::BrowserInformation, Error>;
|
fn get_browser_info(&self) -> Result<types::BrowserInformation, Error>;
|
||||||
fn get_card(&self) -> Result<api::Card, Error>;
|
fn get_card(&self) -> Result<api::Card, Error>;
|
||||||
@ -153,8 +153,12 @@ pub trait PaymentsAuthorizeRequestData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
|
impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
|
||||||
fn is_auto_capture(&self) -> bool {
|
fn is_auto_capture(&self) -> Result<bool, Error> {
|
||||||
self.capture_method == Some(storage_models::enums::CaptureMethod::Automatic)
|
match self.capture_method {
|
||||||
|
Some(storage_models::enums::CaptureMethod::Automatic) | None => Ok(true),
|
||||||
|
Some(storage_models::enums::CaptureMethod::Manual) => Ok(false),
|
||||||
|
Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn get_email(&self) -> Result<Secret<String, Email>, Error> {
|
fn get_email(&self) -> Result<Secret<String, Email>, Error> {
|
||||||
self.email.clone().ok_or_else(missing_field_err("email"))
|
self.email.clone().ok_or_else(missing_field_err("email"))
|
||||||
@ -195,14 +199,32 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PaymentsCompleteAuthorizeRequestData {
|
||||||
|
fn is_auto_capture(&self) -> Result<bool, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData {
|
||||||
|
fn is_auto_capture(&self) -> Result<bool, Error> {
|
||||||
|
match self.capture_method {
|
||||||
|
Some(storage_models::enums::CaptureMethod::Automatic) | None => Ok(true),
|
||||||
|
Some(storage_models::enums::CaptureMethod::Manual) => Ok(false),
|
||||||
|
Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait PaymentsSyncRequestData {
|
pub trait PaymentsSyncRequestData {
|
||||||
fn is_auto_capture(&self) -> bool;
|
fn is_auto_capture(&self) -> Result<bool, Error>;
|
||||||
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError>;
|
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaymentsSyncRequestData for types::PaymentsSyncData {
|
impl PaymentsSyncRequestData for types::PaymentsSyncData {
|
||||||
fn is_auto_capture(&self) -> bool {
|
fn is_auto_capture(&self) -> Result<bool, Error> {
|
||||||
self.capture_method == Some(storage_models::enums::CaptureMethod::Automatic)
|
match self.capture_method {
|
||||||
|
Some(storage_models::enums::CaptureMethod::Automatic) | None => Ok(true),
|
||||||
|
Some(storage_models::enums::CaptureMethod::Manual) => Ok(false),
|
||||||
|
Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError> {
|
fn get_connector_transaction_id(&self) -> CustomResult<String, errors::ValidationError> {
|
||||||
match self.connector_transaction_id.clone() {
|
match self.connector_transaction_id.clone() {
|
||||||
|
|||||||
@ -259,6 +259,8 @@ pub enum ConnectorError {
|
|||||||
},
|
},
|
||||||
#[error("{flow} flow not supported by {connector} connector")]
|
#[error("{flow} flow not supported by {connector} connector")]
|
||||||
FlowNotSupported { flow: String, connector: String },
|
FlowNotSupported { flow: String, connector: String },
|
||||||
|
#[error("Capture method not supported")]
|
||||||
|
CaptureMethodNotSupported,
|
||||||
#[error("Missing connector transaction ID")]
|
#[error("Missing connector transaction ID")]
|
||||||
MissingConnectorTransactionID,
|
MissingConnectorTransactionID,
|
||||||
#[error("Missing connector refund ID")]
|
#[error("Missing connector refund ID")]
|
||||||
|
|||||||
@ -105,7 +105,6 @@ default_imp_for_complete_authorize!(
|
|||||||
connector::Payeezy,
|
connector::Payeezy,
|
||||||
connector::Payu,
|
connector::Payu,
|
||||||
connector::Rapyd,
|
connector::Rapyd,
|
||||||
connector::Shift4,
|
|
||||||
connector::Stripe,
|
connector::Stripe,
|
||||||
connector::Trustpay,
|
connector::Trustpay,
|
||||||
connector::Worldline,
|
connector::Worldline,
|
||||||
|
|||||||
@ -117,6 +117,7 @@ impl types::PaymentsAuthorizeRouterData {
|
|||||||
.execute_pretasks(self, state)
|
.execute_pretasks(self, state)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| error.to_payment_failed_response())?;
|
.map_err(|error| error.to_payment_failed_response())?;
|
||||||
|
if self.should_proceed_with_authorize() {
|
||||||
self.decide_authentication_type();
|
self.decide_authentication_type();
|
||||||
let resp = services::execute_connector_processing_step(
|
let resp = services::execute_connector_processing_step(
|
||||||
state,
|
state,
|
||||||
@ -137,6 +138,9 @@ impl types::PaymentsAuthorizeRouterData {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(mandate::mandate_procedure(state, resp, maybe_customer, pm_id).await?)
|
Ok(mandate::mandate_procedure(state, resp, maybe_customer, pm_id).await?)
|
||||||
|
} else {
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(self.clone()),
|
_ => Ok(self.clone()),
|
||||||
}
|
}
|
||||||
@ -149,6 +153,16 @@ impl types::PaymentsAuthorizeRouterData {
|
|||||||
self.auth_type = storage_models::enums::AuthenticationType::NoThreeDs
|
self.auth_type = storage_models::enums::AuthenticationType::NoThreeDs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// to decide if we need to proceed with authorize or not, Eg: If any of the pretask returns `redirection_response` then we should not proceed with authorize call
|
||||||
|
fn should_proceed_with_authorize(&self) -> bool {
|
||||||
|
match &self.response {
|
||||||
|
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
redirection_data, ..
|
||||||
|
}) => !redirection_data.is_some(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_payment_method<F: Clone, FData>(
|
pub async fn save_payment_method<F: Clone, FData>(
|
||||||
|
|||||||
@ -124,6 +124,7 @@ impl Payments {
|
|||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}")
|
web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}")
|
||||||
|
.route(web::get().to(payments_complete_authorize))
|
||||||
.route(web::post().to(payments_complete_authorize)),
|
.route(web::post().to(payments_complete_authorize)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,10 @@ use std::{
|
|||||||
use actix_web::{body, HttpRequest, HttpResponse, Responder};
|
use actix_web::{body, HttpRequest, HttpResponse, Responder};
|
||||||
use common_utils::errors::ReportSwitchExt;
|
use common_utils::errors::ReportSwitchExt;
|
||||||
use error_stack::{report, IntoReport, Report, ResultExt};
|
use error_stack::{report, IntoReport, Report, ResultExt};
|
||||||
use masking::ExposeOptionInterface;
|
use masking::{ExposeOptionInterface, PeekInterface};
|
||||||
use router_env::{instrument, tracing, Tag};
|
use router_env::{instrument, tracing, Tag};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use self::request::{ContentType, HeaderExt, RequestBuilderExt};
|
use self::request::{ContentType, HeaderExt, RequestBuilderExt};
|
||||||
pub use self::request::{Method, Request, RequestBuilder};
|
pub use self::request::{Method, Request, RequestBuilder};
|
||||||
@ -311,7 +312,13 @@ async fn send_request(
|
|||||||
// Currently this is not used remove this if not required
|
// Currently this is not used remove this if not required
|
||||||
// If using this then handle the serde_part
|
// If using this then handle the serde_part
|
||||||
Some(ContentType::FormUrlEncoded) => {
|
Some(ContentType::FormUrlEncoded) => {
|
||||||
let url_encoded_payload = serde_urlencoded::to_string(&request.payload)
|
let payload = match request.payload.clone() {
|
||||||
|
Some(req) => serde_json::from_str(req.peek())
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ApiClientError::UrlEncodingFailed)?,
|
||||||
|
_ => json!(r#""#),
|
||||||
|
};
|
||||||
|
let url_encoded_payload = serde_urlencoded::to_string(&payload)
|
||||||
.into_report()
|
.into_report()
|
||||||
.change_context(errors::ApiClientError::UrlEncodingFailed)
|
.change_context(errors::ApiClientError::UrlEncodingFailed)
|
||||||
.attach_printable_lazy(|| {
|
.attach_printable_lazy(|| {
|
||||||
|
|||||||
@ -65,7 +65,7 @@ pub type RefundsResponseRouterData<F, R> =
|
|||||||
|
|
||||||
pub type PaymentsAuthorizeType =
|
pub type PaymentsAuthorizeType =
|
||||||
dyn services::ConnectorIntegration<api::Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
|
dyn services::ConnectorIntegration<api::Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
|
||||||
pub type PaymentsComeplteAuthorizeType = dyn services::ConnectorIntegration<
|
pub type PaymentsCompleteAuthorizeType = dyn services::ConnectorIntegration<
|
||||||
api::CompleteAuthorize,
|
api::CompleteAuthorize,
|
||||||
CompleteAuthorizeData,
|
CompleteAuthorizeData,
|
||||||
PaymentsResponseData,
|
PaymentsResponseData,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ async fn should_sync_authorized_payment() {
|
|||||||
txn_id.unwrap(),
|
txn_id.unwrap(),
|
||||||
),
|
),
|
||||||
encoded_data: None,
|
encoded_data: None,
|
||||||
capture_method: None,
|
capture_method: Some(storage_models::enums::CaptureMethod::Manual),
|
||||||
connector_meta: None,
|
connector_meta: None,
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
|
|||||||
Reference in New Issue
Block a user