pub mod transformers; use error_stack::{report, ResultExt}; use masking::{ExposeInterface, Mask}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, request::{Method, Request, RequestBuilder, RequestContent}, }; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{ Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void, }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation, ConnectorSpecifications}, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; use std::sync::LazyLock; use common_enums::enums; use hyperswitch_interfaces::api::ConnectorSpecifications; use hyperswitch_domain_models::router_response_types::{ConnectorInfo, SupportedPaymentMethods}; use crate::{ constants::headers, types::ResponseRouterData, utils, }; use hyperswitch_domain_models::payment_method_data::PaymentMethodData; use transformers as {{project-name | downcase}}; #[derive(Clone)] pub struct {{project-name | downcase | pascal_case}} { amount_converter: &'static (dyn AmountConvertor + Sync) } impl {{project-name | downcase | pascal_case}} { pub fn new() -> &'static Self { &Self { amount_converter: &StringMinorUnitForConnector } } } impl api::Payment for {{project-name | downcase | pascal_case}} {} impl api::PaymentSession for {{project-name | downcase | pascal_case}} {} impl api::ConnectorAccessToken for {{project-name | downcase | pascal_case}} {} impl api::MandateSetup for {{project-name | downcase | pascal_case}} {} impl api::PaymentAuthorize for {{project-name | downcase | pascal_case}} {} impl api::PaymentSync for {{project-name | downcase | pascal_case}} {} impl api::PaymentCapture for {{project-name | downcase | pascal_case}} {} impl api::PaymentVoid for {{project-name | downcase | pascal_case}} {} impl api::Refund for {{project-name | downcase | pascal_case}} {} impl api::RefundExecute for {{project-name | downcase | pascal_case}} {} impl api::RefundSync for {{project-name | downcase | pascal_case}} {} impl api::PaymentToken for {{project-name | downcase | pascal_case}} {} impl ConnectorIntegration< PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} { // Not Implemented (R) } impl ConnectorCommonExt for {{project-name | downcase | pascal_case}} where Self: ConnectorIntegration,{ fn build_headers( &self, req: &RouterData, _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().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 {{project-name | downcase | pascal_case}} { fn id(&self) -> &'static str { "{{project-name | downcase}}" } fn get_currency_unit(&self) -> api::CurrencyUnit { todo!() // TODO! Check connector documentation, on which unit they are processing the currency. // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { "application/json" } fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.{{project-name}}.base_url.as_ref() } fn get_auth_header(&self, auth_type:&ConnectorAuthType)-> CustomResult)>,errors::ConnectorError> { let auth = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}AuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key.expose().into_masked())]) } fn build_error_response( &self, res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { let response: {{project-name | downcase}}::{{project-name | downcase | pascal_case}}ErrorResponse = res .response .parse_struct("{{project-name | downcase | pascal_case}}ErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); Ok(ErrorResponse { status_code: res.status_code, code: response.code, message: response.message, reason: response.reason, attempt_status: None, connector_transaction_id: None, network_advice_code: None, network_decline_code: None, network_error_message: None, connector_metadata: None, }) } } impl ConnectorValidation for {{project-name | downcase | pascal_case}} { fn validate_mandate_payment( &self, _pm_type: Option, pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { match pm_data { PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( "validate_mandate_payment does not support cards".to_string(), ) .into()), _ => Ok(()), } } fn validate_psync_reference_id( &self, _data: &PaymentsSyncData, _is_three_ds: bool, _status: enums::AttemptStatus, _connector_meta_data: Option, ) -> CustomResult<(), errors::ConnectorError> { Ok(()) } } impl ConnectorIntegration< Session, PaymentsSessionData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} { //TODO: implement sessions flow } impl ConnectorIntegration for {{project-name | downcase | pascal_case}} { } impl ConnectorIntegration< SetupMandate, SetupMandateRequestData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} { } impl ConnectorIntegration< Authorize, PaymentsAuthorizeData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} { fn get_headers(&self, req: &PaymentsAuthorizeRouterData, connectors: &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: &PaymentsAuthorizeRouterData, _connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body(&self, req: &PaymentsAuthorizeRouterData, _connectors: &Connectors,) -> CustomResult { let amount = utils::convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, )?; let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::from(( amount, req, )); let connector_req = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, req: &PaymentsAuthorizeRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) .attach_default_headers() .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) .set_body(types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &PaymentsAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { let response: {{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsResponse = res.response.parse_struct("{{project-name | downcase | pascal_case}} PaymentsAuthorizeResponse").change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response(&self, res: Response, event_builder: Option<&mut ConnectorEvent>) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration for {{project-name | downcase | pascal_case}} { fn get_headers( &self, req: &PaymentsSyncRouterData, connectors: &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: &PaymentsSyncRouterData, _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn build_request( &self, req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() .method(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: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { let response: {{project-name | downcase}}:: {{project-name | downcase | pascal_case}}PaymentsResponse = res .response .parse_struct("{{project-name | downcase}} PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response( &self, res: Response, event_builder: Option<&mut ConnectorEvent> ) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration< Capture, PaymentsCaptureData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} { fn get_headers( &self, req: &PaymentsCaptureRouterData, connectors: &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: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, _req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) .set_body(types::PaymentsCaptureType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &PaymentsCaptureRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { let response: {{project-name | downcase }}::{{project-name | downcase | pascal_case}}PaymentsResponse = res .response .parse_struct("{{project-name | downcase | pascal_case}} PaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response( &self, res: Response, event_builder: Option<&mut ConnectorEvent> ) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration< Void, PaymentsCancelData, PaymentsResponseData, > for {{project-name | downcase | pascal_case}} {} impl ConnectorIntegration< Execute, RefundsData, RefundsResponseData, > for {{project-name | downcase | pascal_case}} { fn get_headers(&self, req: &RefundsRouterData, connectors: &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: &RefundsRouterData, _connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body(&self, req: &RefundsRouterData, _connectors: &Connectors,) -> CustomResult { let refund_amount = utils::convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, )?; let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::from(( refund_amount, req, )); let connector_req = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request(&self, req: &RefundsRouterData, connectors: &Connectors,) -> CustomResult,errors::ConnectorError> { let request = RequestBuilder::new() .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers(self, req, connectors)?) .set_body(types::RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult,errors::ConnectorError> { let response: {{project-name| downcase}}::RefundResponse = res.response.parse_struct("{{project-name | downcase}} RefundResponse").change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response(&self, res: Response, event_builder: Option<&mut ConnectorEvent>) -> CustomResult { self.build_error_response(res, event_builder) } } impl ConnectorIntegration for {{project-name | downcase | pascal_case}} { fn get_headers(&self, req: &RefundSyncRouterData,connectors: &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: &RefundSyncRouterData,_connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn build_request( &self, req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) .set_body(types::RefundSyncType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { let response: {{project-name | downcase}}::RefundResponse = res.response.parse_struct("{{project-name | downcase}} RefundSyncResponse").change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, }) } fn get_error_response(&self, res: Response, event_builder: Option<&mut ConnectorEvent>) -> CustomResult { self.build_error_response(res, event_builder) } } #[async_trait::async_trait] impl webhooks::IncomingWebhook for {{project-name | downcase | pascal_case}} { fn get_webhook_object_reference_id( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } static {{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS: LazyLock = LazyLock::new(SupportedPaymentMethods::new); static {{project-name | upcase}}_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { display_name: "{{project-name | downcase | pascal_case}}", description: "{{project-name | downcase | pascal_case}} connector", connector_type: enums::HyperswitchConnectorCategory::PaymentGateway, integration_status: enums::ConnectorIntegrationStatus::Live, }; static {{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; impl ConnectorSpecifications for {{project-name | downcase | pascal_case}} { fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { Some(&{{project-name | upcase}}_CONNECTOR_INFO) } fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { Some(&*{{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS) } fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { Some(&{{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS) } }