diff --git a/config/config.example.toml b/config/config.example.toml index 1b720eaeb42c..05fdb8827632 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -220,6 +220,7 @@ placetopay.base_url = "https://api-co-dev.placetopay.ws/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" diff --git a/config/development.toml b/config/development.toml index 1ce26053fc77..0b7b9756b477 100644 --- a/config/development.toml +++ b/config/development.toml @@ -207,6 +207,7 @@ placetopay.base_url = "https://api-co-dev.placetopay.ws/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 00840daf55fe..35c97e6b5967 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -142,6 +142,7 @@ placetopay.base_url = "https://api-co-dev.placetopay.ws/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index f1b4447c3316..558223a68eed 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -127,6 +127,7 @@ pub enum Connector { Zen, Signifyd, Plaid, + Riskified, } impl Connector { @@ -200,6 +201,7 @@ impl From for RoutableConnectors { pub enum FrmConnectors { /// Signifyd Risk Manager. Official docs: https://docs.signifyd.com/ Signifyd, + Riskified, } #[cfg(feature = "frm")] @@ -207,6 +209,7 @@ impl From for RoutableConnectors { fn from(value: FrmConnectors) -> Self { match value { FrmConnectors::Signifyd => Self::Signifyd, + FrmConnectors::Riskified => Self::Riskified, } } } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b19f4d7b7db1..ef0ae3a15ce6 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -313,6 +313,9 @@ pub struct PaymentsRequest { ///Request for an incremental authorization pub request_incremental_authorization: Option, + + /// additional data related to some frm connectors + pub frm_metadata: Option, } impl PaymentsRequest { @@ -2598,8 +2601,30 @@ pub struct OrderDetailsWithAmount { pub quantity: u16, /// the amount per quantity of product pub amount: i64, + // Does the order includes shipping + pub requires_shipping: Option, /// The image URL of the product pub product_img_link: Option, + /// ID of the product that is being purchased + pub product_id: Option, + /// Category of the product that is being purchased + pub category: Option, + /// Brand of the product that is being purchased + pub brand: Option, + /// Type of the product that is being purchased + pub product_type: Option, +} + +#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ProductType { + #[default] + Physical, + Digital, + Travel, + Ride, + Event, + Accommodation, } #[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -2610,8 +2635,18 @@ pub struct OrderDetails { /// The quantity of the product to be purchased #[schema(example = 1)] pub quantity: u16, + // Does the order include shipping + pub requires_shipping: Option, /// The image URL of the product pub product_img_link: Option, + /// ID of the product that is being purchased + pub product_id: Option, + /// Category of the product that is being purchased + pub category: Option, + /// Brand of the product that is being purchased + pub brand: Option, + /// Type of the product that is being purchased + pub product_type: Option, } #[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index a749744e06df..0c4b9720cab8 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -146,6 +146,7 @@ pub enum RoutableConnectors { Powertranz, Prophetpay, Rapyd, + Riskified, Shift4, Signifyd, Square, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 5b34d56c9388..f73fe099c295 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -618,6 +618,7 @@ pub struct Connectors { pub powertranz: ConnectorParams, pub prophetpay: ConnectorParams, pub rapyd: ConnectorParams, + pub riskified: ConnectorParams, pub shift4: ConnectorParams, pub signifyd: ConnectorParams, pub square: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index e336d8e3514f..de6e250842c7 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -40,6 +40,7 @@ pub mod placetopay; pub mod powertranz; pub mod prophetpay; pub mod rapyd; +pub mod riskified; pub mod shift4; pub mod signifyd; pub mod square; @@ -65,7 +66,8 @@ pub use self::{ iatapay::Iatapay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, placetopay::Placetopay, - powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, shift4::Shift4, - signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, trustpay::Trustpay, tsys::Tsys, - volt::Volt, wise::Wise, worldline::Worldline, worldpay::Worldpay, zen::Zen, + powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, + shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, + trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, + worldpay::Worldpay, zen::Zen, }; diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs new file mode 100644 index 000000000000..e34d12def02a --- /dev/null +++ b/crates/router/src/connector/riskified.rs @@ -0,0 +1,557 @@ +pub mod transformers; +use std::fmt::Debug; + +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, PeekInterface}; +use ring::hmac; +use transformers as riskified; + +#[cfg(feature = "frm")] +use super::utils::FrmTransactionRouterDataRequest; +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + services::{request, ConnectorIntegration, ConnectorValidation}, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + }, +}; +#[cfg(feature = "frm")] +use crate::{ + services, + types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + utils::{self, BytesExt}, +}; + +#[derive(Debug, Clone)] +pub struct Riskified; + +impl Riskified { + pub fn generate_authorization_signature( + &self, + auth: &riskified::RiskifiedAuthType, + payload: &str, + ) -> CustomResult { + let key = hmac::Key::new( + hmac::HMAC_SHA256, + auth.secret_token.clone().expose().as_bytes(), + ); + + let signature_value = hmac::sign(&key, payload.as_bytes()); + + let digest = signature_value.as_ref(); + + Ok(hex::encode(digest)) + } +} + +impl ConnectorCommonExt for Riskified +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let auth: riskified::RiskifiedAuthType = + riskified::RiskifiedAuthType::try_from(&req.connector_auth_type)?; + + let riskified_req = self + .get_request_body(req, connectors)? + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + + let binding = types::RequestBody::get_inner_value(riskified_req); + let payload = binding.peek(); + + let digest = self + .generate_authorization_signature(&auth, payload) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + "X-RISKIFIED-SHOP-DOMAIN".to_string(), + auth.domain_name.clone().into(), + ), + ( + "X-RISKIFIED-HMAC-SHA256".to_string(), + request::Mask::into_masked(digest), + ), + ( + "Accept".to_string(), + "application/vnd.riskified.com; version=2".into(), + ), + ]; + + Ok(header) + } +} + +impl ConnectorCommon for Riskified { + fn id(&self) -> &'static str { + "riskified" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.riskified.base_url.as_ref() + } + + #[cfg(feature = "frm")] + fn build_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: riskified::ErrorResponse = res + .response + .parse_struct("ErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(ErrorResponse { + status_code: res.status_code, + attempt_status: None, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: response.error.message.clone(), + reason: None, + connector_transaction_id: None, + }) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Checkout, + frm_types::FraudCheckCheckoutData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmCheckoutRouterData, + 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: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "/decide")) + } + + fn get_request_body( + &self, + req: &frm_types::FrmCheckoutRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?; + let riskified_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(riskified_req)) + } + + fn build_request( + &self, + req: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmCheckoutType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(frm_types::FrmCheckoutType::get_headers( + self, req, connectors, + )?) + .body(frm_types::FrmCheckoutType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmCheckoutRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedPaymentsResponse = res + .response + .parse_struct("RiskifiedPaymentsResponse Checkout") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::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 api::Payment for Riskified {} +impl api::PaymentAuthorize for Riskified {} +impl api::PaymentSync for Riskified {} +impl api::PaymentVoid for Riskified {} +impl api::PaymentCapture for Riskified {} +impl api::MandateSetup for Riskified {} +impl api::ConnectorAccessToken for Riskified {} +impl api::PaymentToken for Riskified {} +impl api::Refund for Riskified {} +impl api::RefundExecute for Riskified {} +impl api::RefundSync for Riskified {} +impl ConnectorValidation for Riskified {} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Sale, + frm_types::FraudCheckSaleData, + frm_types::FraudCheckResponseData, + > for Riskified +{ +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Transaction, + frm_types::FraudCheckTransactionData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmTransactionRouterData, + 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: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + match req.is_payment_successful() { + Some(false) => Ok(format!( + "{}{}", + self.base_url(connectors), + "/checkout_denied" + )), + Some(true) => Ok(format!("{}{}", self.base_url(connectors), "/decision")), + None => Err(errors::ConnectorError::FlowNotSupported { + flow: "Transaction".to_owned(), + connector: req.connector.to_string(), + })?, + } + } + + fn get_request_body( + &self, + req: &frm_types::FrmTransactionRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + match req.is_payment_successful() { + Some(false) => { + let req_obj = riskified::TransactionFailedRequest::try_from(req)?; + let riskified_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(riskified_req)) + } + Some(true) => { + let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; + let riskified_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(riskified_req)) + } + None => Err(errors::ConnectorError::FlowNotSupported { + flow: "Transaction".to_owned(), + connector: req.connector.to_owned(), + })?, + } + } + + fn build_request( + &self, + req: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmTransactionType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmTransactionType::get_headers( + self, req, connectors, + )?) + .body(frm_types::FrmTransactionType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmTransactionRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedTransactionResponse = res + .response + .parse_struct("RiskifiedPaymentsResponse Transaction") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + match response { + riskified::RiskifiedTransactionResponse::FailedResponse(response_data) => { + ::try_from(types::ResponseRouterData { + response: response_data, + data: data.clone(), + http_code: res.status_code, + }) + } + riskified::RiskifiedTransactionResponse::SuccessResponse(response_data) => { + ::try_from(types::ResponseRouterData { + response: response_data, + data: data.clone(), + http_code: res.status_code, + }) + } + } + } + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > for Riskified +{ + fn get_headers( + &self, + req: &frm_types::FrmFulfillmentRouterData, + 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: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "/fulfill")) + } + + fn get_request_body( + &self, + req: &frm_types::FrmFulfillmentRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = riskified::RiskifiedFullfillmentRequest::try_from(req)?; + let riskified_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(riskified_req)) + } + + fn build_request( + &self, + req: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmFulfillmentType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmFulfillmentType::get_headers( + self, req, connectors, + )?) + .body(frm_types::FrmFulfillmentType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmFulfillmentRouterData, + res: Response, + ) -> CustomResult { + let response: riskified::RiskifiedFulfilmentResponse = res + .response + .parse_struct("RiskifiedFulfilmentResponse fulfilment") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + frm_types::FrmFulfillmentRouterData::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) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::RecordReturn, + frm_types::FraudCheckRecordReturnData, + frm_types::FraudCheckResponseData, + > for Riskified +{ +} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Riskified +{ +} + +impl api::PaymentSession for Riskified {} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +impl ConnectorIntegration + for Riskified +{ +} + +#[cfg(feature = "frm")] +impl api::FraudCheck for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckSale for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckCheckout for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckTransaction for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckFulfillment for Riskified {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckRecordReturn for Riskified {} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Riskified { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/riskified/transformers.rs b/crates/router/src/connector/riskified/transformers.rs new file mode 100644 index 000000000000..4f155f341f6d --- /dev/null +++ b/crates/router/src/connector/riskified/transformers.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "frm")] +pub mod api; +pub mod auth; + +#[cfg(feature = "frm")] +pub use self::api::*; +pub use self::auth::*; diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs new file mode 100644 index 000000000000..de8884f03909 --- /dev/null +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -0,0 +1,597 @@ +use api_models::payments::AdditionalPaymentData; +use common_utils::{ext_traits::ValueExt, pii::Email}; +use error_stack::{self, ResultExt}; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{ + connector::utils::{ + AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckTransactionRequest, RouterData, + }, + core::{errors, fraud_check::types as core_types}, + types::{ + self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + ResponseId, ResponseRouterData, + }, +}; + +type Error = error_stack::Report; + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedPaymentsCheckoutRequest { + order: CheckoutRequest, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct CheckoutRequest { + id: String, + note: Option, + email: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + currency: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + updated_at: PrimitiveDateTime, + gateway: Option, + browser_ip: Option, + total_price: i64, + total_discounts: i64, + cart_token: String, + referring_site: String, + line_items: Vec, + discount_codes: Vec, + shipping_lines: Vec, + payment_details: Option, + customer: RiskifiedCustomer, + billing_address: Option, + shipping_address: Option, + source: Source, + client_details: ClientDetails, + vendor_name: String, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct PaymentDetails { + credit_card_bin: Option>, + credit_card_number: Option>, + credit_card_company: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ShippingLines { + price: i64, + title: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct DiscountCodes { + amount: i64, + code: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ClientDetails { + user_agent: Option, + accept_language: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedCustomer { + email: Option, + first_name: Option>, + last_name: Option>, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + verified_email: bool, + id: String, + account_type: CustomerAccountType, + orders_count: i32, + phone: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum CustomerAccountType { + Guest, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderAddress { + first_name: Option>, + last_name: Option>, + address1: Option>, + country_code: Option, + city: Option, + province: Option>, + phone: Option>, + zip: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct LineItem { + price: i64, + quantity: i32, + title: String, + product_type: Option, + requires_shipping: Option, + product_id: Option, + category: Option, + brand: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +pub enum Source { + DesktopWeb, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedMetadata { + vendor_name: String, + shipping_lines: Vec, +} + +impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutRequest { + type Error = Error; + fn try_from(payment_data: &frm_types::FrmCheckoutRouterData) -> Result { + let metadata: RiskifiedMetadata = payment_data + .frm_metadata + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "frm_metadata", + })? + .parse_value("Riskified Metadata") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let billing_address = payment_data.get_billing_address_with_phone_number()?; + let shipping_address = payment_data.get_shipping_address_with_phone_number()?; + let address = payment_data.get_billing_address()?; + + Ok(Self { + order: CheckoutRequest { + id: payment_data.attempt_id.clone(), + email: payment_data.request.email.clone(), + created_at: common_utils::date_time::now(), + updated_at: common_utils::date_time::now(), + gateway: payment_data.request.gateway.clone(), + total_price: payment_data.request.amount, + cart_token: payment_data.attempt_id.clone(), + line_items: payment_data + .request + .get_order_details()? + .iter() + .map(|order_detail| LineItem { + price: order_detail.amount, + quantity: i32::from(order_detail.quantity), + title: order_detail.product_name.clone(), + product_type: order_detail.product_type.clone(), + requires_shipping: order_detail.requires_shipping, + product_id: order_detail.product_id.clone(), + category: order_detail.category.clone(), + brand: order_detail.brand.clone(), + }) + .collect::>(), + source: Source::DesktopWeb, + billing_address: OrderAddress::try_from(billing_address).ok(), + shipping_address: OrderAddress::try_from(shipping_address).ok(), + total_discounts: 0, + currency: payment_data.request.currency, + referring_site: "hyperswitch.io".to_owned(), + discount_codes: Vec::new(), + shipping_lines: metadata.shipping_lines, + customer: RiskifiedCustomer { + email: payment_data.request.email.clone(), + + first_name: address.get_first_name().ok().cloned(), + last_name: address.get_last_name().ok().cloned(), + created_at: common_utils::date_time::now(), + verified_email: false, + id: payment_data.get_customer_id()?, + account_type: CustomerAccountType::Guest, + orders_count: 0, + phone: billing_address + .clone() + .phone + .and_then(|phone_data| phone_data.number), + }, + browser_ip: payment_data + .request + .browser_info + .as_ref() + .and_then(|browser_info| browser_info.ip_address), + client_details: ClientDetails { + user_agent: payment_data + .request + .browser_info + .as_ref() + .and_then(|browser_info| browser_info.user_agent.clone()), + accept_language: payment_data.request.browser_info.as_ref().and_then( + |browser_info: &types::BrowserInformation| browser_info.language.clone(), + ), + }, + note: payment_data.description.clone(), + vendor_name: metadata.vendor_name, + payment_details: match payment_data.request.payment_method_data.as_ref() { + Some(AdditionalPaymentData::Card(card_info)) => Some(PaymentDetails { + credit_card_bin: card_info.card_isin.clone().map(Secret::new), + credit_card_number: card_info + .last4 + .clone() + .map(|last_four| format!("XXXX-XXXX-XXXX-{}", last_four)) + .map(Secret::new), + credit_card_company: card_info.card_network.clone(), + }), + Some(_) | None => None, + }, + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedPaymentsResponse { + order: OrderResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderResponse { + id: String, + status: PaymentStatus, + description: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFulfilmentResponse { + order: OrderFulfilmentResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderFulfilmentResponse { + id: String, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum FulfilmentStatus { + Fulfilled, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PaymentStatus { + Captured, + Created, + Submitted, + Approved, + Declined, + Processing, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + F, + RiskifiedPaymentsResponse, + T, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order.id), + status: storage_enums::FraudCheckStatus::from(item.response.order.status), + connector_metadata: None, + score: None, + reason: item.response.order.description.map(serde_json::Value::from), + }), + ..item.data + }) + } +} + +impl From for storage_enums::FraudCheckStatus { + fn from(item: PaymentStatus) -> Self { + match item { + PaymentStatus::Approved => Self::Legit, + PaymentStatus::Declined => Self::Fraud, + _ => Self::Pending, + } + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionFailedRequest { + checkout: FailedTransactionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct FailedTransactionData { + id: String, + payment_details: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct DeclinedPaymentDetails { + authorization_error: AuthorizationError, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct AuthorizationError { + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + error_code: Option, + message: Option, +} + +impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionFailedRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + Ok(Self { + checkout: FailedTransactionData { + id: item.attempt_id.clone(), + payment_details: [DeclinedPaymentDetails { + authorization_error: AuthorizationError { + created_at: common_utils::date_time::now(), + error_code: item.request.error_code.clone(), + message: item.request.error_message.clone(), + }, + }] + .to_vec(), + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFailedTransactionResponse { + checkout: OrderResponse, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(untagged)] +pub enum RiskifiedTransactionResponse { + FailedResponse(RiskifiedFailedTransactionResponse), + SuccessResponse(RiskifiedPaymentsResponse), +} + +impl + TryFrom< + ResponseRouterData< + F, + RiskifiedFailedTransactionResponse, + T, + frm_types::FraudCheckResponseData, + >, + > for types::RouterData +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + F, + RiskifiedFailedTransactionResponse, + T, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.checkout.id), + status: storage_enums::FraudCheckStatus::from(item.response.checkout.status), + connector_metadata: None, + score: None, + reason: item + .response + .checkout + .description + .map(serde_json::Value::from), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionSuccessRequest { + order: SuccessfulTransactionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct SuccessfulTransactionData { + id: String, + decision: TransactionDecisionData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionDecisionData { + external_status: TransactionStatus, + reason: Option, + amount: i64, + currency: storage_enums::Currency, + #[serde(with = "common_utils::custom_serde::iso8601")] + decided_at: PrimitiveDateTime, + payment_details: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct TransactionPaymentDetails { + authorization_id: Option, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum TransactionStatus { + Approved, +} + +impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionSuccessRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + Ok(Self { + order: SuccessfulTransactionData { + id: item.attempt_id.clone(), + decision: TransactionDecisionData { + external_status: TransactionStatus::Approved, + reason: None, + amount: item.request.amount, + currency: item.request.get_currency()?, + decided_at: common_utils::date_time::now(), + payment_details: [TransactionPaymentDetails { + authorization_id: item.request.connector_transaction_id.clone(), + }] + .to_vec(), + }, + }, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct RiskifiedFullfillmentRequest { + order: OrderFullfillment, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum FulfillmentRequestStatus { + Success, + Cancelled, + Error, + Failure, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct OrderFullfillment { + id: String, + fulfillments: FulfilmentData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct FulfilmentData { + fulfillment_id: String, + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + status: Option, + tracking_company: String, + tracking_number: String, + tracking_url: Option, +} + +impl TryFrom<&frm_types::FrmFulfillmentRouterData> for RiskifiedFullfillmentRequest { + type Error = Error; + fn try_from(item: &frm_types::FrmFulfillmentRouterData) -> Result { + Ok(Self { + order: OrderFullfillment { + id: item.attempt_id.clone(), + fulfillments: FulfilmentData { + fulfillment_id: item.payment_id.clone(), + created_at: common_utils::date_time::now(), + status: item + .request + .fulfillment_req + .fulfillment_status + .clone() + .and_then(get_fulfillment_status), + tracking_company: item + .request + .fulfillment_req + .tracking_company + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "tracking_company", + })?, + tracking_number: item.request.fulfillment_req.tracking_number.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "tracking_number", + }, + )?, + tracking_url: item.request.fulfillment_req.tracking_url.clone(), + }, + }, + }) + } +} + +impl + TryFrom< + ResponseRouterData< + Fulfillment, + RiskifiedFulfilmentResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + > + for types::RouterData< + Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > +{ + type Error = Error; + fn try_from( + item: ResponseRouterData< + Fulfillment, + RiskifiedFulfilmentResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::FulfillmentResponse { + order_id: item.response.order.id, + shipment_ids: Vec::new(), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ErrorResponse { + pub error: ErrorData, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +pub struct ErrorData { + pub message: String, +} + +impl TryFrom<&api_models::payments::Address> for OrderAddress { + type Error = Error; + fn try_from(address_info: &api_models::payments::Address) -> Result { + let address = + address_info + .clone() + .address + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "address", + })?; + Ok(Self { + first_name: address.first_name.clone(), + last_name: address.last_name.clone(), + address1: address.line1.clone(), + country_code: address.country, + city: address.city.clone(), + province: address.state.clone(), + zip: address.zip.clone(), + phone: address_info + .phone + .clone() + .and_then(|phone_data| phone_data.number), + }) + } +} + +fn get_fulfillment_status( + status: core_types::FulfillmentStatus, +) -> Option { + match status { + core_types::FulfillmentStatus::COMPLETE => Some(FulfillmentRequestStatus::Success), + core_types::FulfillmentStatus::CANCELED => Some(FulfillmentRequestStatus::Cancelled), + core_types::FulfillmentStatus::PARTIAL | core_types::FulfillmentStatus::REPLACEMENT => None, + } +} diff --git a/crates/router/src/connector/riskified/transformers/auth.rs b/crates/router/src/connector/riskified/transformers/auth.rs new file mode 100644 index 000000000000..6968bb55a59c --- /dev/null +++ b/crates/router/src/connector/riskified/transformers/auth.rs @@ -0,0 +1,22 @@ +use error_stack; +use masking::{ExposeInterface, Secret}; + +use crate::{core::errors, types}; + +pub struct RiskifiedAuthType { + pub secret_token: Secret, + pub domain_name: String, +} + +impl TryFrom<&types::ConnectorAuthType> for RiskifiedAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + secret_token: api_key.to_owned(), + domain_name: key1.to_owned().expose(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 5d9714e4d945..ca6e997ba9ef 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -478,10 +478,10 @@ impl req: &frm_types::FrmFulfillmentRouterData, _connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let req_obj = &req.request.fulfillment_request; + let req_obj = signifyd::FrmFullfillmentSignifydRequest::try_from(req)?; let signifyd_req = types::RequestBody::log_and_get_request_body( &req_obj, - utils::Encode::::encode_to_string_of_json, + utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(signifyd_req)) diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 1a1b09bd2880..66d6f0e48cd5 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -11,10 +11,7 @@ use crate::{ AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckRecordReturnRequest, FraudCheckSaleRequest, FraudCheckTransactionRequest, RouterData, }, - core::{ - errors, - fraud_check::types::{self as core_types, FrmFulfillmentRequest}, - }, + core::{errors, fraud_check::types as core_types}, types::{ self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, ResponseId, ResponseRouterData, @@ -356,7 +353,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ #[serde(deny_unknown_fields)] #[serde_with::skip_serializing_none] #[serde(rename_all = "camelCase")] -pub struct FrmFullfillmentSignifydApiRequest { +pub struct FrmFullfillmentSignifydRequest { pub order_id: String, pub fulfillment_status: Option, pub fulfillments: Vec, @@ -391,22 +388,30 @@ pub struct Product { pub item_id: String, } -impl From for FrmFullfillmentSignifydApiRequest { - fn from(req: FrmFulfillmentRequest) -> Self { - Self { - order_id: req.order_id, - fulfillment_status: req.fulfillment_status.map(FulfillmentStatus::from), - fulfillments: req +impl TryFrom<&frm_types::FrmFulfillmentRouterData> for FrmFullfillmentSignifydRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmFulfillmentRouterData) -> Result { + Ok(Self { + order_id: item.request.fulfillment_req.order_id.clone(), + fulfillment_status: item + .request + .fulfillment_req + .fulfillment_status + .clone() + .map(|fulfillment_status| FulfillmentStatus::from(&fulfillment_status)), + fulfillments: item + .request + .fulfillment_req .fulfillments .iter() .map(|f| Fulfillments::from(f.clone())) .collect(), - } + }) } } -impl From for FulfillmentStatus { - fn from(status: core_types::FulfillmentStatus) -> Self { +impl From<&core_types::FulfillmentStatus> for FulfillmentStatus { + fn from(status: &core_types::FulfillmentStatus) -> Self { match status { core_types::FulfillmentStatus::PARTIAL => Self::PARTIAL, core_types::FulfillmentStatus::COMPLETE => Self::COMPLETE, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 3990fc9c7e47..9283ff41f73a 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -10,6 +10,7 @@ use common_utils::{ errors::ReportSwitchExt, pii::{self, Email, IpAddress}, }; +use data_models::payments::payment_attempt::PaymentAttempt; use diesel_models::enums; use error_stack::{report, IntoReport, ResultExt}; use masking::{ExposeInterface, Secret}; @@ -22,13 +23,13 @@ use crate::types::{fraud_check, storage::enums as storage_enums}; use crate::{ consts, core::{ - errors::{self, CustomResult}, + errors::{self, ApiErrorResponse, CustomResult}, payments::PaymentData, }, pii::PeekInterface, types::{ self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom, - ApplePayPredecryptData, PaymentsCancelData, ResponseId, + ApplePayPredecryptData, BrowserInformation, PaymentsCancelData, ResponseId, }, utils::{OptionExt, ValueExt}, }; @@ -67,6 +68,8 @@ pub trait RouterData { fn get_return_url(&self) -> Result; fn get_billing_address(&self) -> Result<&api::AddressDetails, Error>; fn get_shipping_address(&self) -> Result<&api::AddressDetails, Error>; + fn get_billing_address_with_phone_number(&self) -> Result<&api::Address, Error>; + fn get_shipping_address_with_phone_number(&self) -> Result<&api::Address, Error>; fn get_connector_meta(&self) -> Result; fn get_session_token(&self) -> Result; fn to_connector_meta(&self) -> Result @@ -176,6 +179,13 @@ impl RouterData for types::RouterData Result<&api::Address, Error> { + self.address + .billing + .as_ref() + .ok_or_else(missing_field_err("billing")) + } fn get_connector_meta(&self) -> Result { self.connector_meta_data .clone() @@ -211,6 +221,14 @@ impl RouterData for types::RouterData Result<&api::Address, Error> { + self.address + .shipping + .as_ref() + .ok_or_else(missing_field_err("shipping")) + } + fn get_payment_method_token(&self) -> Result { self.payment_method_token .clone() @@ -254,7 +272,7 @@ pub trait PaymentsPreProcessingData { fn get_order_details(&self) -> Result, Error>; fn get_webhook_url(&self) -> Result; fn get_return_url(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { @@ -294,7 +312,7 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { .clone() .ok_or_else(missing_field_err("return_url")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -303,14 +321,14 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { pub trait PaymentsCaptureRequestData { fn is_multiple_capture(&self) -> bool; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsCaptureRequestData for types::PaymentsCaptureData { fn is_multiple_capture(&self) -> bool { self.multiple_capture_data.is_some() } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -318,12 +336,12 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData { } pub trait PaymentsSetupMandateRequestData { - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; fn get_email(&self) -> Result; } impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -335,7 +353,7 @@ impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { pub trait PaymentsAuthorizeRequestData { fn is_auto_capture(&self) -> Result; fn get_email(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_card(&self) -> Result; fn get_return_url(&self) -> Result; @@ -352,11 +370,11 @@ pub trait PaymentsAuthorizeRequestData { } pub trait PaymentMethodTokenizationRequestData { - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentMethodTokenizationRequestData for types::PaymentMethodTokenizationData { - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -374,7 +392,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -480,7 +498,7 @@ pub trait BrowserInformationData { fn get_ip_address(&self) -> Result, Error>; } -impl BrowserInformationData for types::BrowserInformation { +impl BrowserInformationData for BrowserInformation { fn get_ip_address(&self) -> Result, Error> { let ip_address = self .ip_address @@ -588,7 +606,7 @@ pub trait PaymentsCancelRequestData { fn get_amount(&self) -> Result; fn get_currency(&self) -> Result; fn get_cancellation_reason(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsCancelRequestData for PaymentsCancelData { @@ -603,7 +621,7 @@ impl PaymentsCancelRequestData for PaymentsCancelData { .clone() .ok_or_else(missing_field_err("cancellation_reason")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -613,7 +631,7 @@ impl PaymentsCancelRequestData for PaymentsCancelData { pub trait RefundsRequestData { fn get_connector_refund_id(&self) -> Result; fn get_webhook_url(&self) -> Result; - fn get_browser_info(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl RefundsRequestData for types::RefundsData { @@ -629,7 +647,7 @@ impl RefundsRequestData for types::RefundsData { .clone() .ok_or_else(missing_field_err("webhook_url")) } - fn get_browser_info(&self) -> Result { + fn get_browser_info(&self) -> Result { self.browser_info .clone() .ok_or_else(missing_field_err("browser_info")) @@ -1652,3 +1670,83 @@ impl FraudCheckRecordReturnRequest for fraud_check::FraudCheckRecordReturnData { self.currency.ok_or_else(missing_field_err("currency")) } } + +pub trait AccessPaymentAttemptInfo { + fn get_browser_info( + &self, + ) -> Result, error_stack::Report>; +} + +impl AccessPaymentAttemptInfo for PaymentAttempt { + fn get_browser_info( + &self, + ) -> Result, error_stack::Report> { + self.browser_info + .clone() + .map(|b| b.parse_value("BrowserInformation")) + .transpose() + .change_context(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + }) + } +} + +pub trait PaymentsAttemptData { + fn get_browser_info(&self) + -> Result>; +} + +impl PaymentsAttemptData for PaymentAttempt { + fn get_browser_info( + &self, + ) -> Result> { + self.browser_info + .clone() + .ok_or(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + })? + .parse_value::("BrowserInformation") + .change_context(ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + }) + } +} + +#[cfg(feature = "frm")] +pub trait FrmTransactionRouterDataRequest { + fn is_payment_successful(&self) -> Option; +} + +#[cfg(feature = "frm")] +impl FrmTransactionRouterDataRequest for fraud_check::FrmTransactionRouterData { + fn is_payment_successful(&self) -> Option { + match self.status { + storage_enums::AttemptStatus::AuthenticationFailed + | storage_enums::AttemptStatus::RouterDeclined + | storage_enums::AttemptStatus::AuthorizationFailed + | storage_enums::AttemptStatus::Voided + | storage_enums::AttemptStatus::CaptureFailed + | storage_enums::AttemptStatus::Failure + | storage_enums::AttemptStatus::AutoRefunded => Some(false), + + storage_enums::AttemptStatus::AuthenticationSuccessful + | storage_enums::AttemptStatus::PartialChargedAndChargeable + | storage_enums::AttemptStatus::Authorized + | storage_enums::AttemptStatus::Charged => Some(true), + + storage_enums::AttemptStatus::Started + | storage_enums::AttemptStatus::AuthenticationPending + | storage_enums::AttemptStatus::Authorizing + | storage_enums::AttemptStatus::CodInitiated + | storage_enums::AttemptStatus::VoidInitiated + | storage_enums::AttemptStatus::CaptureInitiated + | storage_enums::AttemptStatus::VoidFailed + | storage_enums::AttemptStatus::PartialCharged + | storage_enums::AttemptStatus::Unresolved + | storage_enums::AttemptStatus::Pending + | storage_enums::AttemptStatus::PaymentMethodAwaited + | storage_enums::AttemptStatus::ConfirmationAwaited + | storage_enums::AttemptStatus::DeviceDataCollectionPending => None, + } + } +} diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index f5bb357af0ba..f6aaf22480b9 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -13,7 +13,6 @@ use common_utils::{ use error_stack::{report, FutureExt, IntoReport, ResultExt}; use futures::future::try_join_all; use masking::{PeekInterface, Secret}; -use pm_auth::connector::plaid::transformers::PlaidAuthType; use uuid::Uuid; use crate::{ @@ -1859,10 +1858,12 @@ pub(crate) fn validate_auth_and_metadata_type( signifyd::transformers::SignifydAuthType::try_from(val)?; Ok(()) } - api_enums::Connector::Plaid => { - PlaidAuthType::foreign_try_from(val)?; + api_enums::Connector::Riskified => { + riskified::transformers::RiskifiedAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Plaid => Err(report!(errors::ConnectorError::InvalidConnectorName) + .attach_printable(format!("invalid connector name: {connector_name}"))), } } diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 55bd22baeec4..850c6c8322f8 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -17,7 +17,6 @@ use self::{ }; use super::errors::{ConnectorErrorExt, RouterResponse}; use crate::{ - connector::signifyd::transformers::FrmFullfillmentSignifydApiRequest, core::{ errors::{self, RouterResult}, payments::{ @@ -52,7 +51,7 @@ pub mod types; pub async fn call_frm_service( state: &AppState, payment_data: &mut payments::PaymentData, - frm_data: FrmData, + frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, customer: &Option, @@ -78,7 +77,12 @@ where ) .await?; - let router_data = frm_data + frm_data.payment_attempt.connector_transaction_id = payment_data + .payment_attempt + .connector_transaction_id + .clone(); + + let mut router_data = frm_data .construct_router_data( state, &frm_data.connector_details.connector_name, @@ -88,6 +92,9 @@ where &merchant_connector_account, ) .await?; + + router_data.status = payment_data.payment_attempt.status; + let connector = FraudCheckConnectorData::get_connector_by_name(&frm_data.connector_details.connector_name)?; let router_data_res = router_data @@ -397,6 +404,7 @@ where address: payment_data.address.clone(), connector_details: frm_connector_details.clone(), order_details, + frm_metadata: payment_data.frm_metadata.clone(), }; let fraud_check_operation: operation::BoxedFraudCheckOperation = @@ -722,15 +730,14 @@ pub async fn make_fulfillment_api_call( frm_types::FraudCheckFulfillmentData, frm_types::FraudCheckResponseData, > = connector_data.connector.get_connector_integration(); - let modified_request_for_api_call = FrmFullfillmentSignifydApiRequest::from(req); let router_data = frm_flows::fulfillment_flow::construct_fulfillment_router_data( &state, &payment_intent, &payment_attempt, &merchant_account, &key_store, - "signifyd".to_string(), - modified_request_for_api_call, + fraud_check.frm_name.clone(), + req, ) .await?; let response = services::execute_connector_processing_step( diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index 47a29d657484..7f8993af5270 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -1,9 +1,11 @@ use async_trait::async_trait; -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::ResultExt; +use masking::ExposeInterface; use super::{ConstructFlowSpecificData, FeatureFrm}; use crate::{ + connector::utils::PaymentsAttemptData, core::{ errors::{ConnectorErrorExt, RouterResult}, fraud_check::types::FrmData, @@ -15,7 +17,7 @@ use crate::{ domain, fraud_check::{FraudCheckCheckoutData, FraudCheckResponseData, FrmCheckoutRouterData}, storage::enums as storage_enums, - ConnectorAuthType, ResponseId, RouterData, + BrowserInformation, ConnectorAuthType, ResponseId, RouterData, }, AppState, }; @@ -43,6 +45,7 @@ impl ConstructFlowSpecificData = self.payment_attempt.get_browser_info().ok(); let customer_id = customer.to_owned().map(|customer| customer.customer_id); let router_data = RouterData { @@ -68,6 +71,27 @@ impl ConstructFlowSpecificData( + "AdditionalPaymentData", + ) + }) + .transpose() + .unwrap_or_default(), + email: customer.clone().and_then(|customer_data| { + customer_data + .email + .and_then(|email| Email::try_from(email.into_inner().expose()).ok()) + }), + gateway: self.payment_attempt.connector.clone(), }, // self.order_details response: Ok(FraudCheckResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId("".to_string()), @@ -94,6 +118,7 @@ impl ConstructFlowSpecificData( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, connector: String, - fulfillment_request: FrmFullfillmentSignifydApiRequest, + fulfillment_request: FrmFulfillmentRequest, ) -> RouterResult { let profile_id = core_utils::get_profile_id_from_business_details( payment_intent.business_country, @@ -79,7 +79,7 @@ pub async fn construct_fulfillment_router_data<'a>( request: FraudCheckFulfillmentData { amount: payment_attempt.amount, order_details: payment_intent.order_details.clone(), - fulfillment_request, + fulfillment_req: fulfillment_request, }, response: Err(ErrorResponse::default()), access_token: None, @@ -105,6 +105,7 @@ pub async fn construct_fulfillment_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index eaefdbefcc77..bd0ba3e4f7f4 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -96,6 +96,7 @@ impl ConstructFlowSpecificData for FraudCheckPost { connector_details: payment_data.connector_details, order_details: payment_data.order_details, refund: None, + frm_metadata: payment_data.frm_metadata, }; Ok(Some(frm_data)) } @@ -152,7 +153,7 @@ impl Domain for FraudCheckPost { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -219,7 +220,7 @@ impl Domain for FraudCheckPost { let _router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -243,7 +244,7 @@ impl Domain for FraudCheckPost { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs index 00f50d01a862..b92df3d3ef9f 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -120,6 +120,7 @@ impl GetTracker for FraudCheckPre { connector_details: payment_data.connector_details, order_details: payment_data.order_details, refund: None, + frm_metadata: payment_data.frm_metadata, }; Ok(Some(frm_data)) } @@ -146,7 +147,7 @@ impl Domain for FraudCheckPre { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -163,6 +164,9 @@ impl Domain for FraudCheckPre { order_details: router_data.request.order_details, currency: router_data.request.currency, payment_method: Some(router_data.payment_method), + error_code: router_data.request.error_code, + error_message: router_data.request.error_message, + connector_transaction_id: router_data.request.connector_transaction_id, }), response: FrmResponse::Transaction(router_data.response), })) @@ -180,7 +184,7 @@ impl Domain for FraudCheckPre { let router_data = frm_core::call_frm_service::( state, payment_data, - frm_data.to_owned(), + &mut frm_data.to_owned(), merchant_account, &key_store, customer, @@ -195,6 +199,11 @@ impl Domain for FraudCheckPre { request: FrmRequest::Checkout(FraudCheckCheckoutData { amount: router_data.request.amount, order_details: router_data.request.order_details, + currency: router_data.request.currency, + browser_info: router_data.request.browser_info, + payment_method_data: router_data.request.payment_method_data, + email: router_data.request.email, + gateway: router_data.request.gateway, }), response: FrmResponse::Checkout(router_data.response), }) diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index 1d6e7cb45a58..e60458646f3e 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -56,6 +56,7 @@ pub struct FrmData { pub connector_details: ConnectorDetailsCore, pub order_details: Option>, pub refund: Option, + pub frm_metadata: Option, } #[derive(Debug)] @@ -79,6 +80,7 @@ pub struct PaymentToFrmData { pub address: PaymentAddress, pub connector_details: ConnectorDetailsCore, pub order_details: Option>, + pub frm_metadata: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -123,6 +125,14 @@ pub struct FrmFulfillmentRequest { ///contains details of the fulfillment #[schema(value_type = Vec)] pub fulfillments: Vec, + //name of the tracking Company + #[schema(max_length = 255, example = "fedex")] + pub tracking_company: Option, + //tracking ID of the product + #[schema(max_length = 255, example = "track_8327446667")] + pub tracking_number: Option, + //tracking_url for tracking the product + pub tracking_url: Option, } #[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 73af17f9d66b..3c7ac0fd78d0 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1941,6 +1941,7 @@ where pub payment_link_data: Option, pub incremental_authorization_details: Option, pub authorizations: Vec, + pub frm_metadata: Option, } #[derive(Debug, Default, Clone)] diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 0cb91de05992..394051f1432b 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -173,6 +173,7 @@ default_imp_for_complete_authorize!( connector::Payu, connector::Placetopay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -251,6 +252,7 @@ default_imp_for_webhook_source_verification!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -332,6 +334,7 @@ default_imp_for_create_customer!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -402,6 +405,7 @@ default_imp_for_connector_redirect_response!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -463,6 +467,7 @@ default_imp_for_connector_request_id!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -547,6 +552,7 @@ default_imp_for_accept_dispute!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -649,6 +655,7 @@ default_imp_for_file_upload!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -729,6 +736,7 @@ default_imp_for_submit_evidence!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -809,6 +817,7 @@ default_imp_for_defend_dispute!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -888,6 +897,7 @@ default_imp_for_pre_processing_steps!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Shift4, connector::Signifyd, connector::Square, @@ -951,6 +961,7 @@ default_imp_for_payouts!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1032,6 +1043,7 @@ default_imp_for_payouts_create!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1116,6 +1128,7 @@ default_imp_for_payouts_eligibility!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1197,6 +1210,7 @@ default_imp_for_payouts_fulfill!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1278,6 +1292,7 @@ default_imp_for_payouts_cancel!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1360,6 +1375,7 @@ default_imp_for_payouts_quote!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1442,6 +1458,7 @@ default_imp_for_payouts_recipient!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1523,6 +1540,7 @@ default_imp_for_approve!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -1605,6 +1623,7 @@ default_imp_for_reject!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, @@ -2166,6 +2185,7 @@ default_imp_for_incremental_authorization!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Riskified, connector::Signifyd, connector::Square, connector::Stax, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index e05c60dcf341..47d9c0820d45 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2999,6 +2999,7 @@ pub fn router_data_type_conversion( connector_http_status_code: router_data.connector_http_status_code, external_latency: router_data.external_latency, apple_pay_flow: router_data.apple_pay_flow, + frm_metadata: router_data.frm_metadata, } } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 37a3e1a14123..7d0ec0718c25 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -257,6 +257,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let customer_details = Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 7c8fbcc34979..7e6572ff07dc 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -173,6 +173,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 65b91f0401cf..19998a9a0a71 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -217,6 +217,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 48b503b96b0d..3e91a09ab54e 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -253,6 +253,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let customer_details = Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 612ddadc1c59..8481cd5c8360 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -488,6 +488,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index cbce6ba9e970..87a6b6927513 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -333,6 +333,7 @@ impl payment_link_data, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 693fce236846..9ea347afd735 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -186,6 +186,7 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + frm_metadata: None, }, Some(payments::CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 03bf6dd46b60..5cb3c95dc257 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -160,6 +160,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 572bc710b963..7d9c37339349 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -197,6 +197,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 887edd030d13..67c8579d263a 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -171,6 +171,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 0320cf50663e..44fbdf107818 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -423,6 +423,7 @@ async fn get_tracker_for_sync< frm_message: frm_response.ok(), incremental_authorization_details: None, authorizations, + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 84f11124c730..3ae0aae6d111 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -375,6 +375,7 @@ impl payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], + frm_metadata: request.frm_metadata.clone(), }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 7346c46df120..3a0dfd19a650 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -149,6 +149,7 @@ impl authorization_id: None, }), authorizations: vec![], + frm_metadata: None, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5c280ed72d3b..2aaf0b2957f6 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -165,6 +165,7 @@ where connector_http_status_code: None, external_latency: None, apple_pay_flow, + frm_metadata: None, }; Ok(router_data) @@ -945,6 +946,11 @@ pub fn change_order_details_to_new_type( quantity: order_details.quantity, amount: order_amount, product_img_link: order_details.product_img_link, + requires_shipping: order_details.requires_shipping, + product_id: order_details.product_id, + category: order_details.category, + brand: order_details.brand, + product_type: order_details.product_type, }]) } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 50d9be82794b..016d5ec955d2 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -175,6 +175,7 @@ pub async fn construct_payout_router_data<'a, F>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) @@ -326,6 +327,7 @@ pub async fn construct_refund_router_data<'a, F>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) @@ -555,6 +557,7 @@ pub async fn construct_accept_dispute_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -642,6 +645,7 @@ pub async fn construct_submit_evidence_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -735,6 +739,7 @@ pub async fn construct_upload_file_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -825,6 +830,7 @@ pub async fn construct_defend_dispute_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } @@ -908,6 +914,7 @@ pub async fn construct_retrieve_file_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index 322440e53138..08b490480434 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -113,6 +113,7 @@ pub async fn construct_webhook_router_data<'a>( connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, }; Ok(router_data) } diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 95c36719cad1..c7d3b11abbc8 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -268,6 +268,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SecretInfoToInitiateSdk, api_models::payments::ApplePayPaymentRequest, api_models::payments::AmountInfo, + api_models::payments::ProductType, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, api_models::payments::PaypalRedirection, diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index aa563c647eaa..f50a936aac8b 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -308,6 +308,8 @@ pub struct RouterData { pub external_latency: Option, /// Contains apple pay flow type simplified or manual pub apple_pay_flow: Option, + + pub frm_metadata: Option, } #[derive(Debug, Clone, serde::Deserialize)] @@ -1282,6 +1284,7 @@ impl From<(&RouterData, T2)> connector_http_status_code: data.connector_http_status_code, external_latency: data.external_latency, apple_pay_flow: data.apple_pay_flow.clone(), + frm_metadata: data.frm_metadata.clone(), } } } @@ -1337,6 +1340,7 @@ impl connector_http_status_code: data.connector_http_status_code, external_latency: data.external_latency, apple_pay_flow: None, + frm_metadata: None, } } } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 71751adf6ba4..fcbd3801c944 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -406,7 +406,9 @@ impl ConnectorData { enums::Connector::Tsys => Ok(Box::new(&connector::Tsys)), enums::Connector::Volt => Ok(Box::new(&connector::Volt)), enums::Connector::Zen => Ok(Box::new(&connector::Zen)), - enums::Connector::Signifyd | enums::Connector::Plaid => { + enums::Connector::Signifyd + | enums::Connector::Plaid + | enums::Connector::Riskified => { Err(report!(errors::ConnectorError::InvalidConnectorName) .attach_printable(format!("invalid connector name: {connector_name}"))) .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 7be60bfee952..e871cbc5d330 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -86,6 +86,7 @@ impl FraudCheckConnectorData { ) -> CustomResult { match connector_name { enums::FrmConnectors::Signifyd => Ok(Box::new(&connector::Signifyd)), + enums::FrmConnectors::Riskified => Ok(Box::new(&connector::Riskified)), } } } diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 74b15f911b9a..04a9d76632b0 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -100,6 +100,7 @@ impl VerifyConnectorData { connector_http_status_code: None, external_latency: None, apple_pay_flow: None, + frm_metadata: None, } } } diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs index 4bbba8ac4dca..191e74422f91 100644 --- a/crates/router/src/types/fraud_check.rs +++ b/crates/router/src/types/fraud_check.rs @@ -1,9 +1,13 @@ +use common_utils::pii::Email; + use crate::{ - connector::signifyd::transformers::{FrmFullfillmentSignifydApiRequest, RefundMethod}, + connector::signifyd::transformers::RefundMethod, + core::fraud_check::types::FrmFulfillmentRequest, pii::Serialize, services, - types::{api, storage_enums, ErrorResponse, ResponseId, RouterData}, + types::{self, api, storage_enums, ErrorResponse, ResponseId, RouterData}, }; + pub type FrmSaleRouterData = RouterData; pub type FrmSaleType = @@ -74,6 +78,11 @@ pub type FrmCheckoutType = dyn services::ConnectorIntegration< pub struct FraudCheckCheckoutData { pub amount: i64, pub order_details: Option>, + pub currency: Option, + pub browser_info: Option, + pub payment_method_data: Option, + pub email: Option, + pub gateway: Option, } pub type FrmTransactionRouterData = @@ -91,6 +100,9 @@ pub struct FraudCheckTransactionData { pub order_details: Option>, pub currency: Option, pub payment_method: Option, + pub error_code: Option, + pub error_message: Option, + pub connector_transaction_id: Option, } pub type FrmFulfillmentRouterData = @@ -114,7 +126,7 @@ pub type FrmRecordReturnType = dyn services::ConnectorIntegration< pub struct FraudCheckFulfillmentData { pub amount: i64, pub order_details: Option>>, - pub fulfillment_request: FrmFullfillmentSignifydApiRequest, + pub fulfillment_req: FrmFulfillmentRequest, } #[derive(Debug, Clone)] diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 9f5d46aba426..5bd03fe833af 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -229,6 +229,12 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { }) .into_report()? } + api_enums::Connector::Riskified => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "riskified is not a routable connector".to_string(), + }) + .into_report()? + } api_enums::Connector::Square => Self::Square, api_enums::Connector::Stax => Self::Stax, api_enums::Connector::Stripe => Self::Stripe, diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index dd8c1ed5f778..c46cca8e4dd7 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -95,6 +95,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } @@ -153,6 +154,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index 7de81a8bed28..206694be0e8b 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -78,6 +78,11 @@ fn payment_method_details() -> Option { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -373,6 +378,11 @@ async fn should_fail_payment_for_incorrect_cvc() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -406,6 +416,11 @@ async fn should_fail_payment_for_invalid_exp_month() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -439,6 +454,11 @@ async fn should_fail_payment_for_incorrect_expiry_year() { quantity: 1, amount: 100, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index d3b20b01e4ce..1c384b2e5137 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -523,6 +523,7 @@ pub trait ConnectorActions: Connector { connector_http_status_code: None, apple_pay_flow: None, external_latency: None, + frm_metadata: None, } } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 12f914e13c1a..c3bce7d51c39 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -315,6 +315,11 @@ async fn should_fail_payment_for_incorrect_card_number() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -351,6 +356,11 @@ async fn should_fail_payment_for_incorrect_cvc() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -387,6 +397,11 @@ async fn should_fail_payment_for_invalid_exp_month() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -423,6 +438,11 @@ async fn should_fail_payment_for_incorrect_expiry_year() { quantity: 1, amount: 1000, product_img_link: None, + requires_shipping: None, + product_id: None, + category: None, + brand: None, + product_type: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index cfe8ed561466..ae82a0c094dd 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -43,4 +43,4 @@ actix_web = ["tracing-actix-web"] log_custom_entries_to_extra = [] log_extra_implicit_fields = [] log_active_span_json = [] -payouts = [] +payouts = [] \ No newline at end of file diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index c8c6b1921183..883c243c6c44 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -115,6 +115,7 @@ placetopay.base_url = "https://api-co-dev.placetopay.ws/gateway" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" +riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 3c0206a15896..3ffb98e56b95 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4396,7 +4396,8 @@ "worldpay", "zen", "signifyd", - "plaid" + "plaid", + "riskified" ] }, "ConnectorMetadata": { @@ -8116,10 +8117,37 @@ "example": 1, "minimum": 0 }, + "requires_shipping": { + "type": "boolean", + "nullable": true + }, "product_img_link": { "type": "string", "description": "The image URL of the product", "nullable": true + }, + "product_id": { + "type": "string", + "description": "ID of the product that is being purchased", + "nullable": true + }, + "category": { + "type": "string", + "description": "Category of the product that is being purchased", + "nullable": true + }, + "brand": { + "type": "string", + "description": "Brand of the product that is being purchased", + "nullable": true + }, + "product_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ProductType" + } + ], + "nullable": true } } }, @@ -8149,10 +8177,37 @@ "format": "int64", "description": "the amount per quantity of product" }, + "requires_shipping": { + "type": "boolean", + "nullable": true + }, "product_img_link": { "type": "string", "description": "The image URL of the product", "nullable": true + }, + "product_id": { + "type": "string", + "description": "ID of the product that is being purchased", + "nullable": true + }, + "category": { + "type": "string", + "description": "Category of the product that is being purchased", + "nullable": true + }, + "brand": { + "type": "string", + "description": "Brand of the product that is being purchased", + "nullable": true + }, + "product_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ProductType" + } + ], + "nullable": true } } }, @@ -9788,6 +9843,10 @@ "type": "boolean", "description": "Request for an incremental authorization", "nullable": true + }, + "frm_metadata": { + "description": "additional data related to some frm connectors", + "nullable": true } } }, @@ -10158,6 +10217,10 @@ "type": "boolean", "description": "Request for an incremental authorization", "nullable": true + }, + "frm_metadata": { + "description": "additional data related to some frm connectors", + "nullable": true } } }, @@ -11268,6 +11331,17 @@ } } }, + "ProductType": { + "type": "string", + "enum": [ + "physical", + "digital", + "travel", + "ride", + "event", + "accommodation" + ] + }, "ReceiverDetails": { "type": "object", "required": [