pub mod transformers; use std::fmt::Debug; use common_utils::ext_traits::ByteSliceExt; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as stax; use self::stax::StaxWebhookEventType; use super::utils::{self as connector_utils, to_connector_meta, RefundsRequestData}; use crate::{ configs::settings, consts, core::errors::{self, CustomResult}, headers, services::{ self, request::{self, Mask}, ConnectorIntegration, ConnectorValidation, }, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, domain, ErrorResponse, Response, }, utils::{self, BytesExt}, }; #[derive(Debug, Clone)] pub struct Stax; impl api::Payment for Stax {} impl api::PaymentSession for Stax {} impl api::ConnectorAccessToken for Stax {} impl api::PreVerify for Stax {} impl api::PaymentAuthorize for Stax {} impl api::PaymentSync for Stax {} impl api::PaymentCapture for Stax {} impl api::PaymentVoid for Stax {} impl api::Refund for Stax {} impl api::RefundExecute for Stax {} impl api::RefundSync for Stax {} impl api::PaymentToken for Stax {} impl ConnectorCommonExt for Stax where Self: ConnectorIntegration, { fn build_headers( &self, req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsAuthorizeType::get_content_type(self) .to_string() .into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); Ok(header) } } impl ConnectorCommon for Stax { fn id(&self) -> &'static str { "stax" } fn common_get_content_type(&self) -> &'static str { "application/json" } fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { connectors.stax.base_url.as_ref() } fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, ) -> CustomResult)>, errors::ConnectorError> { let auth = stax::StaxAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Bearer {}", auth.api_key.peek()).into_masked(), )]) } fn build_error_response( &self, res: Response, ) -> CustomResult { Ok(ErrorResponse { status_code: res.status_code, code: consts::NO_ERROR_CODE.to_string(), message: consts::NO_ERROR_MESSAGE.to_string(), reason: Some( std::str::from_utf8(&res.response) .into_report() .change_context(errors::ConnectorError::ResponseDeserializationFailed)? .to_owned(), ), }) } } impl ConnectorValidation for Stax { fn validate_capture_method( &self, capture_method: Option, ) -> 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( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), } } } impl api::ConnectorCustomer for Stax {} impl ConnectorIntegration< api::CreateConnectorCustomer, types::ConnectorCustomerData, types::PaymentsResponseData, > for Stax { fn get_headers( &self, req: &types::ConnectorCustomerRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, _req: &types::ConnectorCustomerRouterData, connectors: &settings::Connectors, ) -> CustomResult { Ok(format!("{}customer", self.base_url(connectors),)) } fn get_request_body( &self, req: &types::ConnectorCustomerRouterData, ) -> CustomResult, errors::ConnectorError> { let connector_request = stax::StaxCustomerRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stax_req)) } fn build_request( &self, req: &types::ConnectorCustomerRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::ConnectorCustomerType::get_url( self, req, connectors, )?) .attach_default_headers() .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) .body(types::ConnectorCustomerType::get_request_body(self, req)?) .build(), )) } fn handle_response( &self, data: &types::ConnectorCustomerRouterData, res: Response, ) -> CustomResult where types::PaymentsResponseData: Clone, { let response: stax::StaxCustomerResponse = res .response .parse_struct("StaxCustomerResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration< api::PaymentMethodToken, types::PaymentMethodTokenizationData, types::PaymentsResponseData, > for Stax { fn get_headers( &self, req: &types::TokenizationRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, _req: &types::TokenizationRouterData, connectors: &settings::Connectors, ) -> CustomResult { Ok(format!("{}payment-method/", self.base_url(connectors))) } fn get_request_body( &self, req: &types::TokenizationRouterData, ) -> CustomResult, errors::ConnectorError> { let connector_request = stax::StaxTokenRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stax_req)) } fn build_request( &self, req: &types::TokenizationRouterData, connectors: &settings::Connectors, ) -> CustomResult, 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 where types::PaymentsResponseData: Clone, { let response: stax::StaxTokenResponse = res .response .parse_struct("StaxTokenResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) .change_context(errors::ConnectorError::ResponseHandlingFailed) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Stax { //TODO: implement sessions flow } impl ConnectorIntegration for Stax { } impl ConnectorIntegration for Stax { } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, 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 { Ok(format!("{}charge", self.base_url(connectors),)) } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, ) -> CustomResult, errors::ConnectorError> { let req_obj = stax::StaxPaymentsRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stax_req)) } fn build_request( &self, req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { self.validate_capture_method(req.request.capture_method)?; Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) .attach_default_headers() .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) .body(types::PaymentsAuthorizeType::get_request_body(self, req)?) .build(), )) } fn handle_response( &self, data: &types::PaymentsAuthorizeRouterData, res: Response, ) -> CustomResult { let response: stax::StaxPaymentsResponse = res .response .parse_struct("StaxPaymentsAuthorizeResponse") .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 { self.build_error_response(res) } } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::PaymentsSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, req: &types::PaymentsSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult { let connector_payment_id = req .request .connector_transaction_id .get_connector_transaction_id() .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; Ok(format!( "{}/transaction/{connector_payment_id}", self.base_url(connectors), )) } fn build_request( &self, req: &types::PaymentsSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &types::PaymentsSyncRouterData, res: Response, ) -> CustomResult { let response: stax::StaxPaymentsResponse = res .response .parse_struct("StaxPaymentsSyncResponse") .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 { self.build_error_response(res) } } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::PaymentsCaptureRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, req: &types::PaymentsCaptureRouterData, connectors: &settings::Connectors, ) -> CustomResult { Ok(format!( "{}/transaction/{}/capture", self.base_url(connectors), req.request.connector_transaction_id, )) } fn get_request_body( &self, req: &types::PaymentsCaptureRouterData, ) -> CustomResult, errors::ConnectorError> { let connector_req = stax::StaxCaptureRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stax_req)) } fn build_request( &self, req: &types::PaymentsCaptureRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) .body(types::PaymentsCaptureType::get_request_body(self, req)?) .build(), )) } fn handle_response( &self, data: &types::PaymentsCaptureRouterData, res: Response, ) -> CustomResult { let response: stax::StaxPaymentsResponse = res .response .parse_struct("StaxPaymentsCaptureResponse") .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 { self.build_error_response(res) } } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::PaymentsCancelRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, 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 { Ok(format!( "{}/transaction/{}/void-or-refund", self.base_url(connectors), req.request.connector_transaction_id, )) } fn build_request( &self, req: &types::PaymentsCancelRouterData, connectors: &settings::Connectors, ) -> CustomResult, 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 { let response: stax::StaxPaymentsResponse = res .response .parse_struct("StaxPaymentsVoidResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; types::RouterData::try_from(types::ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response( &self, res: Response, ) -> CustomResult { self.build_error_response(res) } } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::RefundsRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, req: &types::RefundsRouterData, connectors: &settings::Connectors, ) -> CustomResult { let connector_transaction_id = if req.request.connector_metadata.is_some() { let stax_capture: stax::StaxMetaData = to_connector_meta(req.request.connector_metadata.clone())?; stax_capture.capture_id } else { req.request.connector_transaction_id.clone() }; Ok(format!( "{}/transaction/{}/refund", self.base_url(connectors), connector_transaction_id, )) } fn get_request_body( &self, req: &types::RefundsRouterData, ) -> CustomResult, errors::ConnectorError> { let req_obj = stax::StaxRefundRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stax_req)) } fn build_request( &self, req: &types::RefundsRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { let request = services::RequestBuilder::new() .method(services::Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( self, req, connectors, )?) .body(types::RefundExecuteType::get_request_body(self, req)?) .build(); Ok(Some(request)) } fn handle_response( &self, data: &types::RefundsRouterData, res: Response, ) -> CustomResult, errors::ConnectorError> { let response: stax::RefundResponse = res .response .parse_struct("StaxRefundResponse") .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 { self.build_error_response(res) } } impl ConnectorIntegration for Stax { fn get_headers( &self, req: &types::RefundSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { self.common_get_content_type() } fn get_url( &self, req: &types::RefundSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult { Ok(format!( "{}/transaction/{}", self.base_url(connectors), req.request.get_connector_refund_id()?, )) } fn build_request( &self, req: &types::RefundSyncRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &types::RefundSyncRouterData, res: Response, ) -> CustomResult { let response: stax::RefundResponse = res .response .parse_struct("StaxRefundSyncResponse") .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 { self.build_error_response(res) } } #[async_trait::async_trait] impl api::IncomingWebhook for Stax { async fn verify_webhook_source( &self, _request: &api::IncomingWebhookRequestDetails<'_>, _merchant_account: &domain::MerchantAccount, _merchant_connector_account: domain::MerchantConnectorAccount, _connector_label: &str, ) -> CustomResult { Ok(false) } fn get_webhook_object_reference_id( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let webhook_body: stax::StaxWebhookBody = request .body .parse_struct("StaxWebhookBody") .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; match webhook_body.transaction_type { stax::StaxWebhookEventType::Refund => { Ok(api_models::webhooks::ObjectReferenceId::RefundId( api_models::webhooks::RefundIdType::ConnectorRefundId(webhook_body.id), )) } stax::StaxWebhookEventType::Unknown => { Err(errors::ConnectorError::WebhookEventTypeNotFound.into()) } stax::StaxWebhookEventType::PreAuth | stax::StaxWebhookEventType::Capture | stax::StaxWebhookEventType::Charge | stax::StaxWebhookEventType::Void => { Ok(api_models::webhooks::ObjectReferenceId::PaymentId( api_models::payments::PaymentIdType::ConnectorTransactionId(match webhook_body .transaction_type { stax::StaxWebhookEventType::Capture => webhook_body .auth_id .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?, _ => webhook_body.id, }), )) } } } fn get_webhook_event_type( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let details: stax::StaxWebhookBody = request .body .parse_struct("StaxWebhookEventType") .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; Ok(match &details.transaction_type { StaxWebhookEventType::Refund => match &details.success { true => api::IncomingWebhookEvent::RefundSuccess, false => api::IncomingWebhookEvent::RefundFailure, }, StaxWebhookEventType::Capture | StaxWebhookEventType::Charge => { match &details.success { true => api::IncomingWebhookEvent::PaymentIntentSuccess, false => api::IncomingWebhookEvent::PaymentIntentFailure, } } StaxWebhookEventType::PreAuth | StaxWebhookEventType::Void | StaxWebhookEventType::Unknown => api::IncomingWebhookEvent::EventNotSupported, }) } fn get_webhook_resource_object( &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let reference_object: serde_json::Value = serde_json::from_slice(request.body) .into_report() .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; Ok(reference_object) } }