diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 9e493eb6a5..2737d7a232 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -6330,9 +6330,20 @@ type="Text" api_key="API Key" [silverflow] -[silverflow.connector_auth.BodyKey] +[[silverflow.credit]] + payment_method_type = "Mastercard" +[[silverflow.credit]] + payment_method_type = "Visa" +[[silverflow.debit]] + payment_method_type = "Mastercard" +[[silverflow.debit]] + payment_method_type = "Visa" +[silverflow.connector_auth.SignatureKey] api_key="API Key" -key1="Secret Key" +api_secret="API Secret" +key1="Merchant Acceptor Key" +[silverflow.connector_webhook_details] +merchant_secret="Source verification key" [affirm] [affirm.connector_auth.HeaderKey] @@ -6345,3 +6356,4 @@ api_key = "API Key" [breadpay] [breadpay.connector_auth.HeaderKey] api_key = "API Key" + diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index e81c3f6aac..8bd08758dd 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -4939,9 +4939,20 @@ payment_method_type = "Visa" api_key = "API Key" [silverflow] -[silverflow.connector_auth.BodyKey] -api_key = "API Key" -key1 = "Secret Key" +[[silverflow.credit]] + payment_method_type = "Mastercard" +[[silverflow.credit]] + payment_method_type = "Visa" +[[silverflow.debit]] + payment_method_type = "Mastercard" +[[silverflow.debit]] + payment_method_type = "Visa" +[silverflow.connector_auth.SignatureKey] +api_key="API Key" +api_secret="API Secret" +key1="Merchant Acceptor Key" +[silverflow.connector_webhook_details] +merchant_secret="Source verification key" [affirm] [affirm.connector_auth.HeaderKey] @@ -4954,3 +4965,4 @@ api_key = "API Key" [breadpay] [breadpay.connector_auth.HeaderKey] api_key = "API Key" + diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index bdd9cde57f..e8b9894696 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -6309,9 +6309,20 @@ payment_method_type = "Visa" api_key = "API Key" [silverflow] -[silverflow.connector_auth.BodyKey] -api_key = "API Key" -key1 = "Secret Key" +[[silverflow.credit]] + payment_method_type = "Mastercard" +[[silverflow.credit]] + payment_method_type = "Visa" +[[silverflow.debit]] + payment_method_type = "Mastercard" +[[silverflow.debit]] + payment_method_type = "Visa" +[silverflow.connector_auth.SignatureKey] +api_key="API Key" +api_secret="API Secret" +key1="Merchant Acceptor Key" +[silverflow.connector_webhook_details] +merchant_secret="Source verification key" [affirm] [affirm.connector_auth.HeaderKey] @@ -6324,3 +6335,4 @@ api_key = "API Key" [breadpay] [breadpay.connector_auth.HeaderKey] api_key = "API Key" + diff --git a/crates/hyperswitch_connectors/src/connectors/silverflow.rs b/crates/hyperswitch_connectors/src/connectors/silverflow.rs index 7042380d9e..ac1db77b4d 100644 --- a/crates/hyperswitch_connectors/src/connectors/silverflow.rs +++ b/crates/hyperswitch_connectors/src/connectors/silverflow.rs @@ -2,16 +2,16 @@ pub mod transformers; use std::sync::LazyLock; -use common_enums::enums; +use base64::Engine; +use common_enums::{enums, CardNetwork}; use common_utils::{ + crypto, errors::CustomResult, - ext_traits::BytesExt, + ext_traits::{BytesExt, StringExt}, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, @@ -24,7 +24,8 @@ use hyperswitch_domain_models::{ RefundsData, SetupMandateRequestData, }, router_response_types::{ - ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, @@ -48,15 +49,11 @@ use transformers as silverflow; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] -pub struct Silverflow { - amount_converter: &'static (dyn AmountConvertor + Sync), -} +pub struct Silverflow; impl Silverflow { pub fn new() -> &'static Self { - &Self { - amount_converter: &StringMinorUnitForConnector, - } + &Self } } @@ -76,7 +73,83 @@ impl api::PaymentToken for Silverflow {} impl ConnectorIntegration for Silverflow { - // Not Implemented (R) + fn get_headers( + &self, + req: &RouterData, + 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: &RouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/processorTokens", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + // Create a simplified tokenization request directly from the tokenization data + let connector_req = silverflow::SilverflowTokenizationRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::TokenizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::TokenizationType::get_headers(self, req, connectors)?) + .set_body(types::TokenizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData, + errors::ConnectorError, + > { + let response: silverflow::SilverflowTokenizationResponse = res + .response + .parse_struct("Silverflow TokenizationResponse") + .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 ConnectorCommonExt for Silverflow @@ -88,10 +161,31 @@ where 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 header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::ACCEPT.to_string(), + "application/json".to_string().into(), + ), + ]; + + // Add Idempotency-Key for POST requests (Authorize, Capture, Execute, PaymentMethodToken, Void) + let flow_type = std::any::type_name::(); + if flow_type.contains("Authorize") + || flow_type.contains("Capture") + || flow_type.contains("Execute") + || flow_type.contains("PaymentMethodToken") + || flow_type.contains("Void") + { + header.push(( + "Idempotency-Key".to_string(), + format!("hs_{}", req.payment_id).into(), + )); + } + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); Ok(header) @@ -104,11 +198,8 @@ impl ConnectorCommon for Silverflow { } fn get_currency_unit(&self) -> api::CurrencyUnit { - // todo!() + // Silverflow processes amounts in minor units (cents) api::CurrencyUnit::Minor - // 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 { @@ -125,9 +216,11 @@ impl ConnectorCommon for Silverflow { ) -> CustomResult)>, errors::ConnectorError> { let auth = silverflow::SilverflowAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_string = format!("{}:{}", auth.api_key.expose(), auth.api_secret.expose()); + let encoded = common_utils::consts::BASE64_ENGINE.encode(auth_string.as_bytes()); Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Basic {encoded}").into_masked(), )]) } @@ -146,42 +239,39 @@ impl ConnectorCommon for Silverflow { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.error.code, + message: response.error.message, + reason: response + .error + .details + .map(|d| format!("Field: {}, Issue: {}", d.field, d.issue)), attempt_status: None, connector_transaction_id: None, - network_advice_code: None, network_decline_code: None, + network_advice_code: None, network_error_message: None, }) } } impl ConnectorValidation for Silverflow { - fn validate_mandate_payment( + fn validate_connector_against_payment_request( &self, - _pm_type: Option, - pm_data: PaymentMethodData, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { - match pm_data { - PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( - "validate_mandate_payment does not support cards".to_string(), - ) - .into()), - _ => Ok(()), + 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( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + enums::CaptureMethod::SequentialAutomatic => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), } } - - 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 for Silverflow { @@ -211,9 +301,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/charges", self.base_url(connectors))) } fn get_request_body( @@ -221,13 +311,8 @@ impl ConnectorIntegration CustomResult { - let amount = utils::convert_amount( - self.amount_converter, - req.request.minor_amount, - req.request.currency, - )?; - - let connector_router_data = silverflow::SilverflowRouterData::from((amount, req)); + let connector_router_data = + silverflow::SilverflowRouterData::from((req.request.minor_amount, req)); let connector_req = silverflow::SilverflowPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -298,10 +383,21 @@ impl ConnectorIntegration for Sil fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - 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::MissingRequiredField { + field_name: "connector_transaction_id for payment sync", + })?; + Ok(format!( + "{}/charges/{}", + self.base_url(connectors), + connector_payment_id + )) } fn build_request( @@ -362,18 +458,24 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = &req.request.connector_transaction_id; + Ok(format!( + "{}/charges/{}/clear", + self.base_url(connectors), + connector_payment_id + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let connector_req = silverflow::SilverflowCaptureRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -402,7 +504,7 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: silverflow::SilverflowPaymentsResponse = res + let response: silverflow::SilverflowCaptureResponse = res .response .parse_struct("Silverflow PaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -424,7 +526,89 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Silverflow {} +impl ConnectorIntegration for Silverflow { + fn get_headers( + &self, + req: &RouterData, + 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: &RouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = &req.request.connector_transaction_id; + Ok(format!( + "{}/charges/{}/reverse", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = silverflow::SilverflowVoidRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult< + RouterData, + errors::ConnectorError, + > { + let response: silverflow::SilverflowVoidResponse = res + .response + .parse_struct("Silverflow PaymentsVoidResponse") + .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 Silverflow { fn get_headers( @@ -441,10 +625,15 @@ impl ConnectorIntegration for Silverf fn get_url( &self, - _req: &RefundsRouterData, - _connectors: &Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = &req.request.connector_transaction_id; + Ok(format!( + "{}/charges/{}/refund", + self.base_url(connectors), + connector_payment_id + )) } fn get_request_body( @@ -452,13 +641,8 @@ impl ConnectorIntegration for Silverf 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 = silverflow::SilverflowRouterData::from((refund_amount, req)); + let connector_router_data = + silverflow::SilverflowRouterData::from((req.request.minor_refund_amount, req)); let connector_req = silverflow::SilverflowRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -525,10 +709,19 @@ impl ConnectorIntegration for Silverflo fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + // According to Silverflow API documentation, refunds are actions on charges + // Endpoint: GET /charges/{chargeKey}/actions/{actionKey} + let charge_key = &req.request.connector_transaction_id; + let action_key = &req.request.refund_id; + Ok(format!( + "{}/charges/{}/actions/{}", + self.base_url(connectors), + charge_key, + action_key + )) } fn build_request( @@ -581,36 +774,195 @@ impl ConnectorIntegration for Silverflo impl webhooks::IncomingWebhook for Silverflow { fn get_webhook_object_reference_id( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook_body = String::from_utf8(request.body.to_vec()) + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + + let webhook_event: silverflow::SilverflowWebhookEvent = webhook_body + .parse_struct("SilverflowWebhookEvent") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + + // For payments, use charge_key; for refunds, use refund_key + if let Some(charge_key) = webhook_event.event_data.charge_key { + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::ConnectorTransactionId(charge_key), + )) + } else if let Some(refund_key) = webhook_event.event_data.refund_key { + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::ConnectorRefundId(refund_key), + )) + } else { + Err(errors::ConnectorError::WebhookReferenceIdNotFound.into()) + } } fn get_webhook_event_type( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook_body = String::from_utf8(request.body.to_vec()) + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + + let webhook_event: silverflow::SilverflowWebhookEvent = webhook_body + .parse_struct("SilverflowWebhookEvent") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + + match webhook_event.event_type.as_str() { + "charge.authorization.succeeded" => { + // Handle manual capture flow: check if clearing is still pending + if let Some(status) = webhook_event.event_data.status { + match (&status.authorization, &status.clearing) { + ( + silverflow::SilverflowAuthorizationStatus::Approved, + silverflow::SilverflowClearingStatus::Pending, + ) => { + // Manual capture: authorization succeeded, but clearing is pending + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) + } + ( + silverflow::SilverflowAuthorizationStatus::Approved, + silverflow::SilverflowClearingStatus::Cleared, + ) => { + // Automatic capture: authorization and clearing both completed + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess) + } + _ => { + // Fallback for other authorization states + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) + } + } + } else { + // Fallback when status is not available + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess) + } + } + "charge.authorization.failed" => { + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure) + } + "charge.clearing.succeeded" => { + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess) + } + "charge.clearing.failed" => { + Ok(api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure) + } + "refund.succeeded" => Ok(api_models::webhooks::IncomingWebhookEvent::RefundSuccess), + "refund.failed" => Ok(api_models::webhooks::IncomingWebhookEvent::RefundFailure), + _ => Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported), + } } fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let webhook_body = String::from_utf8(request.body.to_vec()) + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + + let webhook_event: silverflow::SilverflowWebhookEvent = webhook_body + .parse_struct("SilverflowWebhookEvent") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + + Ok(Box::new(webhook_event)) + } + + fn get_webhook_source_verification_algorithm( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let signature = request + .headers + .get("X-Silverflow-Signature") + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)? + .to_str() + .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; + + hex::decode(signature).change_context(errors::ConnectorError::WebhookSignatureNotFound) + } + + fn get_webhook_source_verification_message( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) } } static SILVERFLOW_SUPPORTED_PAYMENT_METHODS: LazyLock = - LazyLock::new(SupportedPaymentMethods::new); + LazyLock::new(|| { + let supported_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::Manual, + ]; + + let supported_card_networks = vec![ + CardNetwork::Visa, + CardNetwork::Mastercard, + CardNetwork::AmericanExpress, + CardNetwork::Discover, + ]; + + let mut silverflow_supported_payment_methods = SupportedPaymentMethods::new(); + + silverflow_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_networks.clone(), + } + }), + ), + }, + ); + + silverflow_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_networks.clone(), + } + }), + ), + }, + ); + + silverflow_supported_payment_methods + }); static SILVERFLOW_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { display_name: "Silverflow", - description: "Silverflow connector", + description: "Silverflow is a global payment processor that provides secure and reliable payment processing services with support for multiple capture methods and 3DS authentication.", connector_type: enums::PaymentConnectorCategory::PaymentGateway, }; -static SILVERFLOW_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; +static SILVERFLOW_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 2] = + [enums::EventClass::Payments, enums::EventClass::Refunds]; impl ConnectorSpecifications for Silverflow { fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { diff --git a/crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs b/crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs index 8a1bb52a5b..87a99417dd 100644 --- a/crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs @@ -1,27 +1,33 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::MinorUnit; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, + router_flow_types::{ + payments::Void, + refunds::{Execute, RSync}, + }, + router_request_types::{PaymentsCancelData, ResponseId}, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; -use crate::types::{RefundsResponseRouterData, ResponseRouterData}; +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::CardData, +}; //TODO: Fill the struct with respective fields pub struct SilverflowRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for SilverflowRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { +impl From<(MinorUnit, T)> for SilverflowRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { //Todo : use utils to convert the amount to the type of amount that a connector accepts Self { amount, @@ -30,20 +36,45 @@ impl From<(StringMinorUnit, T)> for SilverflowRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct SilverflowPaymentsRequest { - amount: StringMinorUnit, - card: SilverflowCard, +// Basic structures for Silverflow API +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Amount { + value: MinorUnit, + currency: String, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct SilverflowCard { +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Card { number: cards::CardNumber, expiry_month: Secret, expiry_year: Secret, cvc: Secret, - complete: bool, + holder_name: Option>, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MerchantAcceptorResolver { + merchant_acceptor_key: String, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowPaymentsRequest { + merchant_acceptor_resolver: MerchantAcceptorResolver, + card: Card, + amount: Amount, + #[serde(rename = "type")] + payment_type: PaymentType, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentType { + intent: String, + card_entry: String, + order: String, } impl TryFrom<&SilverflowRouterData<&PaymentsAuthorizeRouterData>> for SilverflowPaymentsRequest { @@ -52,60 +83,186 @@ impl TryFrom<&SilverflowRouterData<&PaymentsAuthorizeRouterData>> for Silverflow item: &SilverflowRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( - "Card payment method not implemented".to_string(), - ) - .into()), + PaymentMethodData::Card(req_card) => { + // Extract merchant acceptor key from connector auth + let auth = SilverflowAuthType::try_from(&item.router_data.connector_auth_type)?; + + let card = Card { + number: req_card.card_number.clone(), + expiry_month: req_card.card_exp_month.clone(), + expiry_year: req_card.card_exp_year.clone(), + cvc: req_card.card_cvc.clone(), + holder_name: req_card.get_cardholder_name().ok(), + }; + + Ok(Self { + merchant_acceptor_resolver: MerchantAcceptorResolver { + merchant_acceptor_key: auth.merchant_acceptor_key.expose(), + }, + card, + amount: Amount { + value: item.amount, + currency: item.router_data.request.currency.to_string(), + }, + payment_type: PaymentType { + intent: "purchase".to_string(), + card_entry: "e-commerce".to_string(), + order: "checkout".to_string(), + }, + }) + } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct +// Auth Struct for HTTP Basic Authentication pub struct SilverflowAuthType { pub(super) api_key: Secret, + pub(super) api_secret: Secret, + pub(super) merchant_acceptor_key: Secret, } impl TryFrom<&ConnectorAuthType> for SilverflowAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + api_key: api_key.clone(), + api_secret: api_secret.clone(), + merchant_acceptor_key: key1.clone(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] +// Enum for Silverflow payment authorization status +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] -pub enum SilverflowPaymentStatus { - Succeeded, +pub enum SilverflowAuthorizationStatus { + Approved, + Declined, Failed, #[default] - Processing, + Pending, } -impl From for common_enums::AttemptStatus { - fn from(item: SilverflowPaymentStatus) -> Self { - match item { - SilverflowPaymentStatus::Succeeded => Self::Charged, - SilverflowPaymentStatus::Failed => Self::Failure, - SilverflowPaymentStatus::Processing => Self::Authorizing, +// Enum for Silverflow payment clearing status +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SilverflowClearingStatus { + Cleared, + #[default] + Pending, + Failed, +} + +// Payment Authorization Response Structures +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct PaymentStatus { + pub authentication: String, + pub authorization: SilverflowAuthorizationStatus, + pub clearing: SilverflowClearingStatus, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MerchantAcceptorRef { + pub key: String, + pub version: i32, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CardResponse { + pub masked_number: String, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Authentication { + pub sca: SCA, + pub cvc: Secret, + pub avs: String, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SCA { + pub compliance: String, + pub compliance_reason: String, + pub method: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SCAResult { + pub version: String, + pub directory_server_trans_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizationIsoFields { + pub response_code: String, + pub response_code_description: String, + pub authorization_code: String, + pub network_code: String, + pub system_trace_audit_number: Secret, + pub retrieval_reference_number: String, + pub eci: String, + pub network_specific_fields: NetworkSpecificFields, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct NetworkSpecificFields { + pub transaction_identifier: String, + pub cvv2_result_code: String, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowPaymentsResponse { + pub key: String, + pub merchant_acceptor_ref: MerchantAcceptorRef, + pub card: CardResponse, + pub amount: Amount, + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub clearing_mode: String, + pub status: PaymentStatus, + pub authentication: Authentication, + pub local_transaction_date_time: String, + pub fraud_liability: String, + pub authorization_iso_fields: Option, + pub created: String, + pub version: i32, +} + +impl From<&PaymentStatus> for common_enums::AttemptStatus { + fn from(status: &PaymentStatus) -> Self { + match (&status.authorization, &status.clearing) { + (SilverflowAuthorizationStatus::Approved, SilverflowClearingStatus::Cleared) => { + Self::Charged + } + (SilverflowAuthorizationStatus::Approved, SilverflowClearingStatus::Pending) => { + Self::Authorized + } + (SilverflowAuthorizationStatus::Approved, SilverflowClearingStatus::Failed) => { + Self::Failure + } + (SilverflowAuthorizationStatus::Declined, _) => Self::Failure, + (SilverflowAuthorizationStatus::Failed, _) => Self::Failure, + (SilverflowAuthorizationStatus::Pending, _) => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SilverflowPaymentsResponse { - status: SilverflowPaymentStatus, - id: String, -} - impl TryFrom> for RouterData { @@ -114,15 +271,207 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: common_enums::AttemptStatus::from(&item.response.status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::ConnectorTransactionId(item.response.key.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, + network_txn_id: item + .response + .authorization_iso_fields + .as_ref() + .map(|fields| { + fields + .network_specific_fields + .transaction_identifier + .clone() + }), + connector_response_reference_id: Some(item.response.key.clone()), + incremental_authorization_allowed: Some(false), + charges: None, + }), + ..item.data + }) + } +} + +// CAPTURE: +// Type definition for CaptureRequest based on Silverflow API documentation +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowCaptureRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub close_charge: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, +} + +impl TryFrom<&PaymentsCaptureRouterData> for SilverflowCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCaptureRouterData) -> Result { + // amount_to_capture is directly an i64, representing the amount in minor units + let amount_to_capture = Some(item.request.amount_to_capture); + + Ok(Self { + amount: amount_to_capture, + close_charge: Some(true), // Default to closing charge after capture + reference: Some(format!("capture-{}", item.payment_id)), + }) + } +} + +// Enum for Silverflow capture status +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SilverflowCaptureStatus { + Completed, + #[default] + Pending, + Failed, +} + +// Type definition for CaptureResponse based on Silverflow clearing action response +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowCaptureResponse { + #[serde(rename = "type")] + pub action_type: String, + pub key: String, + pub charge_key: String, + pub status: SilverflowCaptureStatus, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, + pub amount: Amount, + pub created: String, + pub last_modified: String, + pub version: i32, +} + +impl From<&SilverflowCaptureResponse> for common_enums::AttemptStatus { + fn from(response: &SilverflowCaptureResponse) -> Self { + match response.status { + SilverflowCaptureStatus::Completed => Self::Charged, + SilverflowCaptureStatus::Pending => Self::Pending, + SilverflowCaptureStatus::Failed => Self::Failure, + } + } +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(&item.response), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.charge_key.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.key.clone()), + incremental_authorization_allowed: Some(false), + charges: None, + }), + ..item.data + }) + } +} + +// VOID/REVERSE: +// Type definition for Reverse Charge Request based on Silverflow API documentation +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowVoidRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub replacement_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, +} + +impl TryFrom<&RouterData> + for SilverflowVoidRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &RouterData, + ) -> Result { + Ok(Self { + replacement_amount: Some(0), // Default to 0 for full reversal + reference: Some(format!("void-{}", item.payment_id)), + }) + } +} + +// Enum for Silverflow void authorization status +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SilverflowVoidAuthorizationStatus { + Approved, + Declined, + Failed, + #[default] + Pending, +} + +// Type definition for Void Status (only authorization, no clearing) +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct VoidStatus { + pub authorization: SilverflowVoidAuthorizationStatus, +} + +// Type definition for Reverse Charge Response based on Silverflow API documentation +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowVoidResponse { + #[serde(rename = "type")] + pub action_type: String, + pub key: String, + pub charge_key: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, + pub replacement_amount: Amount, + pub status: VoidStatus, + pub authorization_response: Option, + pub created: String, + pub last_modified: String, + pub version: i32, +} + +impl From<&SilverflowVoidResponse> for common_enums::AttemptStatus { + fn from(response: &SilverflowVoidResponse) -> Self { + match response.status.authorization { + SilverflowVoidAuthorizationStatus::Approved => Self::Voided, + SilverflowVoidAuthorizationStatus::Declined + | SilverflowVoidAuthorizationStatus::Failed => Self::VoidFailed, + SilverflowVoidAuthorizationStatus::Pending => Self::Pending, + } + } +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(&item.response), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.charge_key.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.key.clone()), + incremental_authorization_allowed: Some(false), charges: None, }), ..item.data @@ -130,50 +479,90 @@ impl TryFrom TryFrom<&SilverflowRouterData<&RefundsRouterData>> for SilverflowRefundRequest { type Error = error_stack::Report; fn try_from(item: &SilverflowRouterData<&RefundsRouterData>) -> Result { Ok(Self { - amount: item.amount.to_owned(), + refund_amount: item.amount, + reference: format!("refund-{}", item.router_data.request.refund_id), }) } } -// Type definition for Refund Response +// Type definition for Authorization Response +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizationResponse { + pub network: String, -#[allow(dead_code)] -#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, + pub response_code: String, + + pub response_code_description: String, } -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { +// Enum for Silverflow refund authorization status +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SilverflowRefundAuthorizationStatus { + Approved, + Declined, + Failed, + Pending, +} + +// Enum for Silverflow refund status +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SilverflowRefundStatus { + Success, + Failure, + #[default] + Pending, +} + +impl From<&SilverflowRefundStatus> for enums::RefundStatus { + fn from(item: &SilverflowRefundStatus) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + SilverflowRefundStatus::Success => Self::Success, + SilverflowRefundStatus::Failure => Self::Failure, + SilverflowRefundStatus::Pending => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +// Type definition for Refund Response +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + #[serde(rename = "type")] + pub action_type: String, + pub key: String, + pub charge_key: String, + pub reference: String, + pub amount: Amount, + pub status: SilverflowRefundStatus, + pub clear_after: Option, + pub authorization_response: Option, + pub created: String, + pub last_modified: String, + pub version: i32, } impl TryFrom> for RefundsRouterData { @@ -183,8 +572,8 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.key.clone(), + refund_status: enums::RefundStatus::from(&item.response.status), }), ..item.data }) @@ -198,22 +587,189 @@ impl TryFrom> for RefundsRouter ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.key.clone(), + refund_status: enums::RefundStatus::from(&item.response.status), }), ..item.data }) } } -//TODO: Fill the struct with respective fields +// TOKENIZATION: +// Type definition for TokenizationRequest +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowTokenizationRequest { + pub reference: String, + + pub card_data: SilverflowCardData, +} + +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowCardData { + pub number: String, + pub expiry_month: Secret, + pub expiry_year: Secret, + pub cvc: String, + pub holder_name: String, +} + +impl TryFrom<&PaymentsAuthorizeRouterData> for SilverflowTokenizationRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsAuthorizeRouterData) -> Result { + match item.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card_data = SilverflowCardData { + number: req_card.card_number.peek().to_string(), + expiry_month: req_card.card_exp_month.clone(), + expiry_year: req_card.card_exp_year.clone(), + cvc: req_card.card_cvc.clone().expose(), + holder_name: req_card + .get_cardholder_name() + .unwrap_or(Secret::new("".to_string())) + .expose(), + }; + + Ok(Self { + reference: format!("CUSTOMER_ID_{}", item.payment_id), + card_data, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +// Add TryFrom implementation for direct tokenization router data +impl + TryFrom< + &RouterData< + hyperswitch_domain_models::router_flow_types::payments::PaymentMethodToken, + hyperswitch_domain_models::router_request_types::PaymentMethodTokenizationData, + PaymentsResponseData, + >, + > for SilverflowTokenizationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &RouterData< + hyperswitch_domain_models::router_flow_types::payments::PaymentMethodToken, + hyperswitch_domain_models::router_request_types::PaymentMethodTokenizationData, + PaymentsResponseData, + >, + ) -> Result { + match item.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card_data = SilverflowCardData { + number: req_card.card_number.peek().to_string(), + expiry_month: req_card.card_exp_month.clone(), + expiry_year: req_card.card_exp_year.clone(), + cvc: req_card.card_cvc.clone().expose(), + holder_name: req_card + .get_cardholder_name() + .unwrap_or(Secret::new("".to_string())) + .expose(), + }; + + Ok(Self { + reference: format!("CUSTOMER_ID_{}", item.payment_id), + card_data, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +// Type definition for TokenizationResponse +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SilverflowTokenizationResponse { + pub key: String, + #[serde(rename = "agentKey")] + pub agent_key: String, + pub last4: String, + pub status: String, + pub reference: String, + #[serde(rename = "cardInfo")] + pub card_info: Vec, + pub created: String, + #[serde(rename = "cvcPresent")] + pub cvc_present: bool, + pub version: i32, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CardInfo { + #[serde(rename = "infoSource")] + pub info_source: String, + pub network: String, + #[serde(rename = "primaryNetwork")] + pub primary_network: bool, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(PaymentsResponseData::TokenizationResponse { + token: item.response.key, + }), + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TokenizedCardDetails { + pub masked_card_number: String, + pub expiry_month: Secret, + pub expiry_year: Secret, + pub card_brand: String, +} + +// WEBHOOKS: +// Type definition for Webhook Event structures +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowWebhookEvent { + pub event_type: String, + pub event_data: SilverflowWebhookEventData, + pub event_id: String, + pub created: String, + pub version: i32, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SilverflowWebhookEventData { + pub charge_key: Option, + pub refund_key: Option, + pub status: Option, + pub amount: Option, + pub transaction_reference: Option, +} + +// Error Response Structures based on Silverflow API format +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct ErrorDetails { + pub field: String, + pub issue: String, +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct SilverflowErrorResponse { - pub status_code: u16, + pub error: SilverflowError, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct SilverflowError { pub code: String, pub message: String, - pub reason: Option, - pub network_advice_code: Option, - pub network_decline_code: Option, - pub network_error_message: Option, + pub details: Option, } diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 1d45f8fb6f..605be65581 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -101,7 +101,7 @@ pub struct ConnectorAuthentication { pub redsys: Option, pub santander: Option, pub shift4: Option, - pub silverflow: Option, + pub silverflow: Option, pub square: Option, pub stax: Option, pub stripe: Option,