diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 09346e388f..5f5181693f 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -14417,6 +14417,14 @@ "type": "integer", "nullable": true }, + "poll_config": { + "allOf": [ + { + "$ref": "#/components/schemas/PollConfig" + } + ], + "nullable": true + }, "type": { "type": "string", "enum": [ @@ -20620,6 +20628,27 @@ } } }, + "PollConfig": { + "type": "object", + "required": [ + "delay_in_secs", + "frequency" + ], + "properties": { + "delay_in_secs": { + "type": "integer", + "format": "int32", + "description": "Interval of the poll", + "minimum": 0 + }, + "frequency": { + "type": "integer", + "format": "int32", + "description": "Frequency of the poll", + "minimum": 0 + } + } + }, "PollConfigResponse": { "type": "object", "required": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 743ddf8254..c63c5485c5 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -17394,6 +17394,14 @@ "type": "integer", "nullable": true }, + "poll_config": { + "allOf": [ + { + "$ref": "#/components/schemas/PollConfig" + } + ], + "nullable": true + }, "type": { "type": "string", "enum": [ @@ -25301,6 +25309,27 @@ } } }, + "PollConfig": { + "type": "object", + "required": [ + "delay_in_secs", + "frequency" + ], + "properties": { + "delay_in_secs": { + "type": "integer", + "format": "int32", + "description": "Interval of the poll", + "minimum": 0 + }, + "frequency": { + "type": "integer", + "format": "int32", + "description": "Frequency of the poll", + "minimum": 0 + } + } + }, "PollConfigResponse": { "type": "object", "required": [ diff --git a/config/config.example.toml b/config/config.example.toml index 9ba9118d5a..7eddf9a9e8 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -270,7 +270,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 89ec016645..2065f7a99a 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -107,7 +107,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" shift4.base_url = "https://api.shift4.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index a7f80eba15..bd0340300a 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -111,7 +111,7 @@ plaid.base_url = "https://production.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://api.rapyd.net" -razorpay.base_url = "https://api.juspay.in" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis.redsys.es" riskified.base_url = "https://wh.riskified.com/api/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index bd2862fccd..7ba862a875 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -111,7 +111,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/config/development.toml b/config/development.toml index a9989da8d0..d7fb9578c9 100644 --- a/config/development.toml +++ b/config/development.toml @@ -302,7 +302,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 8d06922754..8d538dbd0d 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -196,7 +196,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/config/payment_required_fields_v2.toml b/config/payment_required_fields_v2.toml index 4ae591d363..cc9ffee29e 100644 --- a/config/payment_required_fields_v2.toml +++ b/config/payment_required_fields_v2.toml @@ -2090,7 +2090,10 @@ non_mandate = [ # Payment method type: UpiCollect [required_fields.upi.upi_collect.fields.Razorpay] common = [ - { required_field = "payment_method_data.upi.vpa_id", display_name = "vpa_id", field_type = "text" } + { required_field = "payment_method_data.upi.vpa_id", display_name = "vpa_id", field_type = "text" }, + { required_field = "payment_method_data.billing.email", display_name = "email", field_type = "user_email_address" }, + { required_field = "payment_method_data.billing.phone.number", display_name = "phone", field_type = "user_phone_number" }, + { required_field = "payment_method_data.billing.phone.country_code", display_name = "dialing_code", field_type = "user_phone_number_country_code" } ] mandate = [] non_mandate = [] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index eb153ff9cd..47c1b5a83d 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4537,6 +4537,7 @@ pub enum NextActionData { WaitScreenInformation { display_from_timestamp: i128, display_to_timestamp: Option, + poll_config: Option, }, /// Contains the information regarding three_ds_method_data submission, three_ds authentication, and authorization flows ThreeDsInvoke { @@ -4697,6 +4698,15 @@ pub struct QrCodeNextStepsInstruction { pub struct WaitScreenInstructions { pub display_from_timestamp: i128, pub display_to_timestamp: Option, + pub poll_config: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct PollConfig { + /// Interval of the poll + pub delay_in_secs: u16, + /// Frequency of the poll + pub frequency: u16, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 411b873233..ea7f7e740a 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -371,6 +371,9 @@ impl Connector { | (Self::Facilitapay, _) ) } + pub fn requires_order_creation_before_payment(self, payment_method: PaymentMethod) -> bool { + matches!((self, payment_method), (Self::Razorpay, PaymentMethod::Upi)) + } pub fn supports_file_storage_module(self) -> bool { matches!(self, Self::Stripe | Self::Checkout) } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index a694f3fc0c..fa373d2305 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -112,6 +112,7 @@ pub struct PaymentAttempt { pub network_decline_code: Option, /// A string indicating how to proceed with an network error if payment gateway provide one. This is used to understand the network error code better. pub network_error_message: Option, + pub connector_request_reference_id: Option, } #[cfg(feature = "v1")] @@ -340,6 +341,7 @@ pub struct PaymentAttemptNew { pub network_error_message: Option, pub processor_merchant_id: Option, pub created_by: Option, + pub connector_request_reference_id: Option, } #[cfg(feature = "v1")] @@ -871,6 +873,7 @@ pub struct PaymentAttemptUpdateInternal { pub network_decline_code: Option, pub network_advice_code: Option, pub network_error_message: Option, + pub connector_request_reference_id: Option, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 0c6bdadac8..882c620d9f 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -913,6 +913,8 @@ diesel::table! { #[max_length = 32] network_decline_code -> Nullable, network_error_message -> Nullable, + #[max_length = 255] + connector_request_reference_id -> Nullable, } } diff --git a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs index 756f94d41d..95043f3925 100644 --- a/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs @@ -2,7 +2,7 @@ use api_models::payouts::{self, PayoutMethodData}; use api_models::{ enums, - payments::{self, QrCodeInformation, VoucherNextStepData}, + payments::{self, PollConfig, QrCodeInformation, VoucherNextStepData}, }; use cards::CardNumber; use common_enums::enums as storage_enums; @@ -4229,6 +4229,7 @@ pub fn get_qr_metadata( pub struct WaitScreenData { display_from_timestamp: i128, display_to_timestamp: Option, + poll_config: Option, } pub fn get_wait_screen_metadata( @@ -4239,14 +4240,16 @@ pub fn get_wait_screen_metadata( let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos(); Ok(Some(serde_json::json!(WaitScreenData { display_from_timestamp: current_time, - display_to_timestamp: Some(current_time + Duration::minutes(1).whole_nanoseconds()) + display_to_timestamp: Some(current_time + Duration::minutes(1).whole_nanoseconds()), + poll_config: None }))) } PaymentType::Mbway => { let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos(); Ok(Some(serde_json::json!(WaitScreenData { display_from_timestamp: current_time, - display_to_timestamp: None + display_to_timestamp: None, + poll_config: None }))) } PaymentType::Affirm diff --git a/crates/hyperswitch_connectors/src/connectors/razorpay.rs b/crates/hyperswitch_connectors/src/connectors/razorpay.rs index 3974da2c6b..56080d73fb 100644 --- a/crates/hyperswitch_connectors/src/connectors/razorpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/razorpay.rs @@ -1,33 +1,35 @@ pub mod transformers; -use api_models::webhooks::{self, IncomingWebhookEvent}; +use base64::Engine; use common_enums::enums; use common_utils::{ errors::CustomResult, - ext_traits::{ByteSliceExt, BytesExt}, + ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; -use error_stack::{Report, ResultExt}; +use error_stack::{report, Report, ResultExt}; use hyperswitch_domain_models::{ - router_data::{AccessToken, ErrorResponse, RouterData}, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, CreateOrder, PSync, PaymentMethodToken, Session, SetupMandate, Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ - AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + AccessTokenRequestData, CreateOrderRequestData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{ ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + CreateOrderRouterData, PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -39,11 +41,11 @@ use hyperswitch_interfaces::{ consts::NO_ERROR_CODE, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, CreateOrderType, Response}, webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; use lazy_static::lazy_static; -use masking::{ExposeInterface, Mask}; +use masking::{Mask, Maskable, PeekInterface}; use router_env::logger; use transformers as razorpay; @@ -55,13 +57,13 @@ use crate::{ #[derive(Clone)] pub struct Razorpay { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Razorpay { pub fn new() -> &'static Self { &Self { - amount_converter: &FloatMajorUnitForConnector, + amount_converter: &MinorUnitForConnector, } } } @@ -91,19 +93,15 @@ where { fn build_headers( &self, - _req: &RouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - let header = vec![ - ( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - ), - ( - headers::AUTHORIZATION.to_string(), - format!("Basic {}", connectors.razorpay.api_key.clone().expose()).into_masked(), - ), - ]; + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + "application/json".to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); Ok(header) } } @@ -125,6 +123,23 @@ impl ConnectorCommon for Razorpay { connectors.razorpay.base_url.as_ref() } + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = razorpay::RazorpayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = common_utils::consts::BASE64_ENGINE.encode(format!( + "{}:{}", + auth.razorpay_id.peek(), + auth.razorpay_secret.peek() + )); + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Basic {encoded_api_key}").into_masked(), + )]) + } + fn build_error_response( &self, res: Response, @@ -141,23 +156,9 @@ impl ConnectorCommon for Razorpay { razorpay::ErrorResponse::RazorpayErrorResponse(error_response) => { Ok(ErrorResponse { status_code: res.status_code, - code: error_response.error_info.code, - message: error_response - .error_info - .fields - .iter() - .map(|error| error.field_name.clone()) - .collect::>() - .join(" & "), - reason: Some( - error_response - .error_info - .fields - .iter() - .map(|error| error.field_name.clone()) - .collect::>() - .join(" & "), - ), + code: error_response.error.code, + message: error_response.error.description, + reason: error_response.error.reason, attempt_status: None, connector_transaction_id: None, network_advice_code: None, @@ -165,6 +166,17 @@ impl ConnectorCommon for Razorpay { network_error_message: None, }) } + razorpay::ErrorResponse::RazorpayError(error_response) => Ok(ErrorResponse { + status_code: res.status_code, + code: error_response.message.clone(), + message: error_response.message.clone(), + reason: Some(error_response.message), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + }), razorpay::ErrorResponse::RazorpayStringError(error_string) => { Ok(ErrorResponse { status_code: res.status_code, @@ -204,12 +216,93 @@ impl ConnectorIntegration for Razorpay { + fn get_headers( + &self, + req: &CreateOrderRouterData, + 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: &CreateOrderRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}v1/orders", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &CreateOrderRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let minor_amount = req.request.minor_amount; + let currency = req.request.currency; + let amount = convert_amount(self.amount_converter, minor_amount, currency)?; + let connector_router_data = razorpay::RazorpayRouterData::try_from((amount, req))?; + let connector_req = razorpay::RazorpayOrderRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &CreateOrderRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(CreateOrderType::get_headers(self, req, connectors)?) + .url(&CreateOrderType::get_url(self, req, connectors)?) + .set_body(CreateOrderType::get_request_body(self, req, connectors)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &CreateOrderRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: razorpay::RazorpayOrderResponse = res + .response + .parse_struct("RazorpayOrderResponse") + .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 Razorpay { fn get_headers( &self, req: &PaymentsAuthorizeRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -223,7 +316,7 @@ impl ConnectorIntegration CustomResult { Ok(format!( - "{}gatewayProxy/txn/sendCollect", + "{}v1/payments/create/json", self.base_url(connectors) )) } @@ -231,7 +324,7 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -239,8 +332,7 @@ impl ConnectorIntegration for Raz &self, req: &PaymentsSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -308,30 +400,20 @@ impl ConnectorIntegration for Raz } fn get_url( - &self, - _req: &PaymentsSyncRouterData, - connectors: &Connectors, - ) -> CustomResult { - Ok(format!( - "{}gatewayProxy/sync/transaction", - self.base_url(connectors) - )) - } - - fn get_request_body( &self, req: &PaymentsSyncRouterData, connectors: &Connectors, - ) -> CustomResult { - let amount = convert_amount( - self.amount_converter, - req.request.amount, - req.request.currency, - )?; - let connector_router_data = razorpay::RazorpayRouterData::try_from((amount, req))?; - let connector_req = - razorpay::RazorpayCreateSyncRequest::try_from((connector_router_data, connectors))?; - Ok(RequestContent::Json(Box::new(connector_req))) + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}v1/payments/{}", + self.base_url(connectors), + connector_payment_id, + )) } fn build_request( @@ -340,7 +422,7 @@ impl ConnectorIntegration for Raz connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { let request = RequestBuilder::new() - .method(Method::Post) + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -384,7 +466,7 @@ impl ConnectorIntegration fo &self, req: &PaymentsCaptureRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -463,7 +545,7 @@ impl ConnectorIntegration for Razorpa &self, req: &RefundsRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -473,16 +555,20 @@ impl ConnectorIntegration for Razorpa fn get_url( &self, - _req: &RefundsRouterData, + req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult { - Ok(format!("{}gatewayProxy/refund", self.base_url(connectors))) + Ok(format!( + "{}v1/payments/{}/refund", + self.base_url(connectors), + req.request.connector_transaction_id, + )) } fn get_request_body( &self, req: &RefundsRouterData, - connectors: &Connectors, + _connectors: &Connectors, ) -> CustomResult { let amount = convert_amount( self.amount_converter, @@ -490,8 +576,7 @@ impl ConnectorIntegration for Razorpa req.request.currency, )?; let connector_router_data = razorpay::RazorpayRouterData::try_from((amount, req))?; - let connector_req = - razorpay::RazorpayRefundRequest::try_from((&connector_router_data, connectors))?; + let connector_req = razorpay::RazorpayRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -520,9 +605,9 @@ impl ConnectorIntegration for Razorpa event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: razorpay::RefundResponse = res + let response: razorpay::RazorpayRefundResponse = res .response - .parse_struct("razorpay RefundResponse") + .parse_struct("razorpay RazorpayRefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -547,7 +632,7 @@ impl ConnectorIntegration for Razorpay &self, req: &RefundSyncRouterData, connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -556,30 +641,21 @@ impl ConnectorIntegration for Razorpay } fn get_url( - &self, - _req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult { - Ok(format!( - "{}gatewayProxy/sync/refund", - self.base_url(connectors) - )) - } - - fn get_request_body( &self, req: &RefundSyncRouterData, connectors: &Connectors, - ) -> CustomResult { - let amount = convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; - let connector_router_data = razorpay::RazorpayRouterData::try_from((amount, req))?; - let connector_req = - razorpay::RazorpayRefundRequest::try_from((&connector_router_data, connectors))?; - Ok(RequestContent::Json(Box::new(connector_req))) + ) -> CustomResult { + let refund_id = req + .request + .connector_refund_id + .to_owned() + .ok_or(errors::ConnectorError::MissingConnectorRefundID)?; + Ok(format!( + "{}v1/payments/{}/refunds/{}", + self.base_url(connectors), + req.request.connector_transaction_id, + refund_id + )) } fn build_request( @@ -589,7 +665,7 @@ impl ConnectorIntegration for Razorpay ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Post) + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -606,9 +682,9 @@ impl ConnectorIntegration for Razorpay event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: razorpay::RefundResponse = res + let response: razorpay::RazorpayRefundResponse = res .response - .parse_struct("razorpay RefundSyncResponse") + .parse_struct("razorpay RazorpayRefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -628,65 +704,91 @@ impl ConnectorIntegration for Razorpay } } +// This code can be used later when Razorpay webhooks are implemented + +// #[async_trait::async_trait] +// impl IncomingWebhook for Razorpay { +// fn get_webhook_object_reference_id( +// &self, +// request: &IncomingWebhookRequestDetails<'_>, +// ) -> CustomResult { +// let webhook_resource_object: razorpay::RazorpayWebhookPayload = request +// .body +// .parse_struct("RazorpayWebhookPayload") +// .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + +// match webhook_resource_object.payload.refund { +// Some(refund_data) => Ok(webhooks::ObjectReferenceId::RefundId( +// webhooks::RefundIdType::ConnectorRefundId(refund_data.entity.id), +// )), +// None => Ok(webhooks::ObjectReferenceId::PaymentId( +// api_models::payments::PaymentIdType::ConnectorTransactionId( +// webhook_resource_object.payload.payment.entity.id, +// ), +// )), +// } +// } + +// async fn verify_webhook_source( +// &self, +// _request: &IncomingWebhookRequestDetails<'_>, +// _merchant_id: &common_utils::id_type::MerchantId, +// _connector_webhook_details: Option, +// _connector_account_details: common_utils::crypto::Encryptable< +// masking::Secret, +// >, +// _connector_label: &str, +// ) -> CustomResult { +// Ok(false) +// } + +// fn get_webhook_event_type( +// &self, +// request: &IncomingWebhookRequestDetails<'_>, +// ) -> CustomResult { +// let payload: razorpay::RazorpayWebhookEventType = request +// .body +// .parse_struct("RazorpayWebhookEventType") +// .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; +// Ok(IncomingWebhookEvent::try_from(payload)?) +// } + +// fn get_webhook_resource_object( +// &self, +// request: &IncomingWebhookRequestDetails<'_>, +// ) -> CustomResult, errors::ConnectorError> { +// let details: razorpay::RazorpayWebhookPayload = request +// .body +// .parse_struct("RazorpayWebhookPayload") +// .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; +// Ok(Box::new(details)) +// } +// } + #[async_trait::async_trait] impl IncomingWebhook for Razorpay { fn get_webhook_object_reference_id( - &self, - request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - let webhook_resource_object = get_webhook_object_from_body(request.body)?; - match webhook_resource_object.refund { - Some(refund_data) => Ok(webhooks::ObjectReferenceId::RefundId( - webhooks::RefundIdType::ConnectorRefundId(refund_data.entity.id), - )), - None => Ok(webhooks::ObjectReferenceId::PaymentId( - api_models::payments::PaymentIdType::ConnectorTransactionId( - webhook_resource_object.payment.entity.id, - ), - )), - } - } - - async fn verify_webhook_source( &self, _request: &IncomingWebhookRequestDetails<'_>, - _merchant_id: &common_utils::id_type::MerchantId, - _connector_webhook_details: Option, - _connector_account_details: common_utils::crypto::Encryptable< - masking::Secret, - >, - _connector_label: &str, - ) -> CustomResult { - Ok(false) + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - let webhook_resource_object = get_webhook_object_from_body(request.body)?; - Ok(IncomingWebhookEvent::try_from(webhook_resource_object)?) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(api_models::webhooks::IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - request: &IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - let webhook_resource_object = get_webhook_object_from_body(request.body)?; - Ok(Box::new(webhook_resource_object)) + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } -fn get_webhook_object_from_body( - body: &[u8], -) -> CustomResult { - let details: razorpay::RazorpayWebhookEvent = body - .parse_struct("RazorpayWebhookEvent") - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - - Ok(details.payload) -} - lazy_static! { static ref RAZORPAY_SUPPORTED_PAYMENT_METHODS: SupportedPaymentMethods = { let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; @@ -729,4 +831,14 @@ impl ConnectorSpecifications for Razorpay { fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { Some(&*RAZORPAY_SUPPORTED_WEBHOOK_FLOWS) } + + #[cfg(feature = "v2")] + fn generate_connector_request_reference_id( + &self, + payment_intent: &hyperswitch_domain_models::payments::PaymentIntent, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + ) -> String { + // The length of receipt for Razorpay order request should not exceed 40 characters. + uuid::Uuid::now_v7().to_string() + } } diff --git a/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs index fea15be71b..23aa882829 100644 --- a/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs @@ -1,39 +1,41 @@ +use std::collections::HashMap; + +use api_models::payments::PollConfig; use common_enums::enums; use common_utils::{ - pii::{self, Email}, - types::FloatMajorUnit, + errors::CustomResult, + pii::{self, Email, IpAddress}, + types::MinorUnit, }; -use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::{PaymentMethodData, UpiCollectData, UpiData}, + payment_method_data::{PaymentMethodData, UpiData}, router_data::{ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, types, }; -use hyperswitch_interfaces::{ - configs::Connectors, - consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, - errors, -}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; -use time::PrimitiveDateTime; +use time::{Duration, OffsetDateTime}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::generate_12_digit_number, + types::{CreateOrderResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + get_unimplemented_payment_method_error_message, missing_field_err, + PaymentsAuthorizeRequestData, RouterData as OtherRouterData, + }, }; pub struct RazorpayRouterData { - pub amount: FloatMajorUnit, + pub amount: MinorUnit, pub router_data: T, } -impl TryFrom<(FloatMajorUnit, T)> for RazorpayRouterData { +impl TryFrom<(MinorUnit, T)> for RazorpayRouterData { type Error = error_stack::Report; - fn try_from((amount, item): (FloatMajorUnit, T)) -> Result { + fn try_from((amount, item): (MinorUnit, T)) -> Result { Ok(Self { amount, router_data: item, @@ -44,608 +46,156 @@ impl TryFrom<(FloatMajorUnit, T)> for RazorpayRouterData { pub const VERSION: i32 = 1; #[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RazorpayPaymentsRequest { - second_factor: SecondFactor, - merchant_account: MerchantAccount, - order_reference: OrderReference, - txn_detail: TxnDetail, - txn_card_info: TxnCardInfo, - merchant_gateway_account: MerchantGatewayAccount, - gateway: Gateway, - transaction_create_req: TransactionCreateReq, - is_mesh_enabled: bool, - order_metadata_v2: OrderMetadataV2, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct SecondFactor { - txn_id: String, - id: String, - status: SecondFactorStatus, - #[serde(rename = "type")] - sf_type: SecondFactorType, - version: i32, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_updated: Option, - transaction_id: Option, - url: Option, - epg_txn_id: Option, - transaction_detail_id: Option, - gateway_auth_required_params: Option, - authentication_account_id: Option, - can_accept_response: Option, - challenges_attempted: Option, - response_attempted: Option, - partition_key: Option, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum SecondFactorType { - Otp, - #[default] - Vbv, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum SecondFactorStatus { - Pending, - #[default] - Init, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct MerchantAccount { - id: u64, - merchant_id: Secret, - use_code_for_gateway_priority: bool, - auto_refund_multiple_charged_transactions: bool, - gateway_success_rate_based_outage_input: Option, - gateway_success_rate_based_decider_input: Option, - card_encoding_key: Option, - enable_unauthenticated_order_status_api: Option, - enabled_instant_refund: Option, - enable_reauthentication: Option, - return_url: Option, - credit_balance: Option, - internal_metadata: Option, - gateway_decided_by_health_enabled: Option, - zip: Option, - enable_3d_secure_help_mail: Option, - payout_mid: Option, - gateway_priority_logic: Option, - enable_success_rate_based_gateway_elimination: Option, - otp_enabled: Option, - enable_sending_card_isin: Option, - state: Option, - must_use_given_order_id_for_txn: Option, - gateway_priority: Option, - timezone: Option, - user_id: Option, - office_line_1: Option, - enable_save_card_before_auth: Option, - office_line_2: Option, - merchant_legal_name: Option, - settlement_account_id: Option, - external_metadata: Option, - office_line_3: Option, - enable_payment_response_hash: Option, - prefix_merchant_id_for_card_key: Option, - admin_contact_email: Option, - enable_reauthorization: Option, - locker_id: Option, - enable_recapture: Option, - contact_person_email: Option, - basilisk_key_id: Option, - whitelabel_enabled: Option, - inline_checkout_enabled: Option, - payu_merchant_key: Option, - encryption_key_ids: Option, - enable_gateway_reference_id_based_routing: Option, - enabled: Option, - enable_automatic_retry: Option, - about_business: Option, - redirect_to_merchant_with_http_post: Option, - webhook_api_version: Option, - express_checkout_enabled: Option, - city: Option, - webhook_url: Option, - webhook_username: Option, - webhook_custom_headers: Option, - reverse_token_enabled: Option, - webhook_configs: Option, - last_modified: Option, - network_token_locker_id: Option, - enable_sending_last_four_digits: Option, - website: Option, - mobile: Option, - webhook_password: Option, - reseller_id: Option, - mobile_version: Option, - contact_person_primary: Option, - conflict_status_email: Option, - payu_test_mode: Option, - payment_response_hash_key: Option, - enable_refunds_in_dashboard: Option, - tenant_account_id: Option, - merchant_name: Option, - hdfc_test_mode: Option, - enable_unauthenticated_card_add: Option, - payu_salt: Option, - api_key: Option, - date_created: Option, - internal_hash_key: Option, - version: Option, - mandatory_2fa: Option, -} - -#[derive(Default, Debug, Serialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct OrderReference { - id: String, - amount: FloatMajorUnit, - currency: String, - order_id: String, - status: OrderStatus, - merchant_id: Secret, - order_uuid: String, - order_type: OrderType, - version: i32, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_modified: Option, - return_url: Option, - billing_address_id: Option, - internal_metadata: Option, - mandate_feature: Option, - udf6: Option, - udf1: Option, - partition_key: Option, - amount_refunded: Option, - customer_phone: Option, - description: Option, - customer_email: Option, - customer_id: Option, - refunded_entirely: Option, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum OrderType { - MandatePayment, - #[default] - OrderPayment, -} - -#[derive(Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum MandateFeature { - Disabled, - Required, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum OrderStatus { - Success, - #[default] - PendingAuthentication, -} - -#[derive(Default, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TxnDetail { - status: TxnStatus, - merchant_id: Secret, - txn_id: String, - express_checkout: bool, - is_emi: bool, - net_amount: FloatMajorUnit, - txn_amount: FloatMajorUnit, - emi_tenure: i32, - txn_uuid: String, - currency: String, - version: i32, - redirect: bool, - id: String, - #[serde(rename = "type")] - txn_type: TxnType, - order_id: String, - add_to_locker: bool, - merchant_gateway_account_id: u64, - txn_mode: TxnMode, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_modified: Option, - gateway: Gateway, - internal_metadata: Option, - txn_flow_type: Option, - source_object: Option, - partition_key: Option, - username: Option, - txn_object_type: Option, - internal_tracking_info: Option, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum TxnType { - AuthAndSettle, - #[default] - PreAuthAndSettle, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum TxnMode { - Prod, - #[default] - Test, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct TxnCardInfo { - txn_detail_id: String, - txn_id: String, - payment_method_type: String, - id: String, - payment_method: String, - payment_source: Secret, - date_created: Option, - partition_key: Option, - card_type: Option, - card_issuer_bank_name: Option, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct MerchantGatewayAccount { - merchant_id: Secret, - gateway: Gateway, - account_details: String, - version: i32, - id: u64, - test_mode: bool, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_modified: Option, - disabled: bool, - payment_methods: Option, - enforce_payment_method_acceptance: Option, - supported_payment_flows: Option, - is_juspay_account: Option, -} - -#[derive(Debug, Serialize, Eq, PartialEq)] #[serde(rename_all = "snake_case")] -pub struct TransactionCreateReq { - merchant_id: Secret, +pub struct RazorpayOrderRequest { + pub amount: MinorUnit, + pub currency: enums::Currency, + pub receipt: String, + pub partial_payment: Option, + pub first_payment_min_amount: Option, + pub notes: Option, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct OrderMetadataV2 { - id: String, - order_reference_id: String, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_updated: Option, - browser: Option, - operating_system: Option, - ip_address: Option, - partition_key: Option, - user_agent: Option, - browser_version: Option, - mobile: Option, - metadata: Option, +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RazorpayNotes { + Map(HashMap), + EmptyMap(HashMap), } -#[derive(Default, Debug, Serialize, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum Gateway { - #[default] - Razorpay, - YesBiz, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct RazorpayCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - )> for RazorpayPaymentsRequest -{ +impl TryFrom<&RazorpayRouterData<&types::CreateOrderRouterData>> for RazorpayOrderRequest { type Error = error_stack::Report; fn try_from( - (item, data): ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - ), + item: &RazorpayRouterData<&types::CreateOrderRouterData>, ) -> Result { - let request = &item.router_data.request; - let txn_card_info = match request.payment_method_data.clone() { - PaymentMethodData::Upi(upi_type) => match upi_type { - UpiData::UpiCollect(upi_data) => TxnCardInfo::try_from((item, upi_data)), - UpiData::UpiIntent(_) => Err(errors::ConnectorError::NotImplemented( - "Payment methods".to_string(), - ) - .into()), - }, - PaymentMethodData::Card(_) - | PaymentMethodData::CardRedirect(_) - | PaymentMethodData::Wallet(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::BankRedirect(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::Reward - | PaymentMethodData::RealTimePayment(_) - | PaymentMethodData::MobilePayment(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::OpenBanking(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) - } - }?; - let merchant_data = JuspayAuthData::try_from(data)?; - let second_factor = SecondFactor::try_from(item)?; - let merchant_account = MerchantAccount::try_from((item, data))?; - let order_reference = OrderReference::try_from((item, data))?; - let txn_detail = TxnDetail::try_from((item, data))?; - let merchant_gateway_account = MerchantGatewayAccount::try_from((item, data))?; - let gateway = Gateway::Razorpay; - let transaction_create_req = TransactionCreateReq { - merchant_id: merchant_data.merchant_id, - }; - let is_mesh_enabled = false; - let order_metadata_v2 = OrderMetadataV2::try_from(item)?; + let currency = item.router_data.request.currency; + let receipt = item.router_data.connector_request_reference_id.clone(); Ok(Self { - second_factor, - merchant_account, - order_reference, - txn_detail, - txn_card_info, - merchant_gateway_account, - gateway, - transaction_create_req, - is_mesh_enabled, - order_metadata_v2, + amount: item.amount, + currency, + receipt, + partial_payment: None, + first_payment_min_amount: None, + notes: None, }) } } -impl TryFrom<&RazorpayRouterData<&types::PaymentsAuthorizeRouterData>> for SecondFactor { - type Error = error_stack::Report; +#[derive(Debug, Serialize, Deserialize)] +pub struct RazorpayMetaData { + pub order_id: String, +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct RazorpayOrderResponse { + pub id: String, +} + +impl TryFrom> + for types::CreateOrderRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: CreateOrderResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(PaymentsResponseData::PaymentsCreateOrderResponse { + order_id: item.response.id.clone(), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct UpiDetails { + flow: UpiFlow, + vpa: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum UpiFlow { + Collect, + Intent, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct RazorpayPaymentsRequest { + amount: MinorUnit, + currency: enums::Currency, + order_id: String, + email: Email, + contact: Secret, + method: RazorpayPaymentMethod, + upi: UpiDetails, + #[serde(skip_serializing_if = "Option::is_none")] + ip: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + user_agent: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RazorpayPaymentMethod { + Upi, +} + +impl TryFrom<&RazorpayRouterData<&types::PaymentsAuthorizeRouterData>> for RazorpayPaymentsRequest { + type Error = error_stack::Report; fn try_from( item: &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { - let ref_id = generate_12_digit_number(); + let payment_router_data = item.router_data; + let router_request = &payment_router_data.request; + let payment_method_data = &router_request.payment_method_data; + + let (razorpay_payment_method, upi_details) = match payment_method_data { + PaymentMethodData::Upi(upi_type_data) => match upi_type_data { + UpiData::UpiCollect(upi_collect_data) => { + let vpa_secret = upi_collect_data + .vpa_id + .clone() + .ok_or_else(missing_field_err("payment_method_data.upi.collect.vpa_id"))?; + ( + RazorpayPaymentMethod::Upi, + UpiDetails { + flow: UpiFlow::Collect, + vpa: vpa_secret, + }, + ) + } + UpiData::UpiIntent(_upi_intent_data) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("razorpay"), + ))? + } + }, + _ => Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("razorpay"), + ))?, + }; + + let contact_number = item.router_data.get_billing_phone_number()?; + let order_id = router_request.get_order_id()?; + let email = item.router_data.get_billing_email()?; + let ip = router_request.get_ip_address_as_optional(); + let user_agent = router_request.get_optional_user_agent(); Ok(Self { - txn_id: item.router_data.connector_request_reference_id.clone(), - id: ref_id.to_string(), - status: SecondFactorStatus::Pending, - version: VERSION, - sf_type: SecondFactorType::Vbv, - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - ..Default::default() - }) - } -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - )> for MerchantAccount -{ - type Error = error_stack::Report; - - fn try_from( - (_item, data): ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - ), - ) -> Result { - let merchant_data = JuspayAuthData::try_from(data)?; - let ref_id = generate_12_digit_number(); - Ok(Self { - id: ref_id, - merchant_id: merchant_data.merchant_id, - auto_refund_multiple_charged_transactions: false, - use_code_for_gateway_priority: true, - ..Default::default() - }) - } -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - )> for OrderReference -{ - type Error = error_stack::Report; - - fn try_from( - (item, data): ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - ), - ) -> Result { - let ref_id = generate_12_digit_number(); - let merchant_data = JuspayAuthData::try_from(data)?; - Ok(Self { - id: ref_id.to_string(), amount: item.amount, - currency: item.router_data.request.currency.to_string(), - status: OrderStatus::PendingAuthentication, - merchant_id: merchant_data.merchant_id.clone(), - order_id: item.router_data.connector_request_reference_id.clone(), - version: VERSION, - order_type: OrderType::OrderPayment, - order_uuid: uuid::Uuid::new_v4().to_string(), - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }) - } -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - UpiCollectData, - )> for TxnCardInfo -{ - type Error = error_stack::Report; - fn try_from( - payment_data: ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - UpiCollectData, - ), - ) -> Result { - let item = payment_data.0; - let upi_data = payment_data.1; - let ref_id = generate_12_digit_number(); - let pm = enums::PaymentMethod::Upi; - Ok(Self { - txn_detail_id: ref_id.to_string(), - txn_id: item.router_data.connector_request_reference_id.clone(), - payment_method_type: pm.to_string().to_uppercase(), - id: ref_id.to_string(), - payment_method: pm.to_string().to_uppercase(), - payment_source: upi_data.vpa_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "vpa_id", - }, - )?, - date_created: Some(common_utils::date_time::now()), - ..Default::default() - }) - } -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - )> for TxnDetail -{ - type Error = error_stack::Report; - - fn try_from( - (item, data): ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - ), - ) -> Result { - let ref_id = generate_12_digit_number(); - let merchant_data = JuspayAuthData::try_from(data)?; - let txn_mode: TxnMode = match item.router_data.test_mode { - Some(true) | None => TxnMode::Test, - Some(false) => TxnMode::Prod, - }; - Ok(Self { - order_id: item.router_data.connector_request_reference_id.clone(), - express_checkout: false, - txn_mode, - merchant_id: merchant_data.merchant_id, - status: TxnStatus::PendingVbv, - net_amount: item.amount, - txn_id: item.router_data.connector_request_reference_id.clone(), - txn_amount: item.amount, - emi_tenure: 0, - txn_uuid: uuid::Uuid::new_v4().to_string(), - id: ref_id.to_string(), - merchant_gateway_account_id: ref_id, - txn_type: TxnType::AuthAndSettle, - redirect: true, - version: VERSION, - add_to_locker: false, - currency: item.router_data.request.currency.to_string(), - is_emi: false, - gateway: Gateway::Razorpay, - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }) - } -} - -impl - TryFrom<( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - )> for MerchantGatewayAccount -{ - type Error = error_stack::Report; - - fn try_from( - (item, data): ( - &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &Connectors, - ), - ) -> Result { - let ref_id = generate_12_digit_number(); - let auth = RazorpayAuthType::try_from(&item.router_data.connector_auth_type)?; - let merchant_data = JuspayAuthData::try_from(data)?; - let account_details = AccountDetails { - razorpay_id: auth.razorpay_id.clone(), - razorpay_secret: auth.razorpay_secret, - }; - Ok(Self { - merchant_id: merchant_data.merchant_id, - gateway: Gateway::Razorpay, - disabled: false, - id: ref_id, - account_details: serde_json::to_string(&account_details) - .change_context(errors::ConnectorError::ParsingFailed)?, - test_mode: false, - ..Default::default() - }) - } -} -impl TryFrom<&RazorpayRouterData<&types::PaymentsAuthorizeRouterData>> for OrderMetadataV2 { - type Error = error_stack::Report; - - fn try_from( - _item: &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - ) -> Result { - let ref_id = generate_12_digit_number(); - Ok(Self { - id: ref_id.to_string(), - order_reference_id: ref_id.to_string(), - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - ..Default::default() + currency: router_request.currency, + order_id, + email, + contact: contact_number, + method: razorpay_payment_method, + upi: upi_details, + ip, + user_agent, }) } } @@ -668,109 +218,16 @@ impl TryFrom<&ConnectorAuthType> for RazorpayAuthType { } } -pub struct JuspayAuthData { - pub(super) merchant_id: Secret, -} -impl TryFrom<&Connectors> for JuspayAuthData { - type Error = error_stack::Report; - - fn try_from(connector_param: &Connectors) -> Result { - let Connectors { razorpay, .. } = connector_param; - Ok(Self { - merchant_id: razorpay.merchant_id.clone(), - }) - } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NextAction { + pub action: String, + pub url: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RazorpayPaymentsResponse { - contents: Contents, - tag: Tag, - code: u16, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Tag { - Stateless, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiMetadata { - ext_api_tag: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Contents { - send_webhook: bool, - second_factor: Option, - pgr_response: Option, - api_metadata: ApiMetadata, - pgr_info: PgrInfo, - txn_status: TxnStatus, - #[serde(rename = "updatePGR")] - update_pgr: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SecondFactorResponse { - id: u64, - #[serde(rename = "type")] - sf_type: String, - version: i32, - date_created: String, - last_updated: String, - epg_txn_id: String, - status: String, - txn_id: String, - authentication_account_id: Option, - can_accept_response: Option, - challenges_attempted: Option, - gateway_auth_req_params: Option, - partition_key: Option, - response_attempted: Option, - txn_detail_id: Option, - url: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PgrInfo { - resp_code: String, - resp_message: Option, - response_xml: String, - resptype: Option, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum TxnStatus { - Charged, - Authorizing, - #[default] - PendingVbv, -} - -impl From for enums::AttemptStatus { - fn from(item: TxnStatus) -> Self { - match item { - TxnStatus::Authorizing => Self::Pending, - TxnStatus::PendingVbv => Self::Failure, - TxnStatus::Charged => Self::Charged, - } - } -} -impl From for TxnStatus { - fn from(item: enums::AttemptStatus) -> Self { - match item { - enums::AttemptStatus::Pending => Self::Authorizing, - enums::AttemptStatus::Failure => Self::PendingVbv, - enums::AttemptStatus::Charged => Self::Charged, - _ => Self::PendingVbv, - } - } + pub razorpay_payment_id: String, + pub next: Option>, } impl TryFrom> @@ -780,219 +237,70 @@ impl TryFrom, ) -> Result { - let second_factor = item.response.contents.second_factor; - let status = enums::AttemptStatus::from(item.response.contents.txn_status); - match second_factor { - Some(second_factor) => Ok(Self { - status, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(second_factor.epg_txn_id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some(second_factor.txn_id), - incremental_authorization_allowed: None, - charges: None, - }), - ..item.data - }), - None => { - let message_code = item - .response - .contents - .pgr_info - .resp_message - .clone() - .unwrap_or(NO_ERROR_MESSAGE.to_string()); - Ok(Self { - status, - response: Err(hyperswitch_domain_models::router_data::ErrorResponse { - code: item.response.contents.pgr_info.resp_code.clone(), - message: message_code.clone(), - reason: Some(message_code.clone()), - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: None, - network_advice_code: None, - network_decline_code: None, - network_error_message: None, - }), - ..item.data - }) - } - } - } -} + let connector_metadata = get_wait_screen_metadata()?; -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RazorpayCreateSyncRequest { - txn_detail: TxnDetail, - merchant_gateway_account: MerchantGatewayAccount, - order_reference: OrderReference, - second_factor: SecondFactor, - gateway_txn_data: GatewayTxnData, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GatewayTxnData { - id: String, - version: i32, - gateway_data: String, - gateway_status: String, - match_status: String, - txn_detail_id: String, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - last_updated: Option, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct AccountDetails { - razorpay_id: Secret, - razorpay_secret: Secret, -} - -impl - TryFrom<( - RazorpayRouterData<&types::PaymentsSyncRouterData>, - &Connectors, - )> for RazorpayCreateSyncRequest -{ - type Error = error_stack::Report; - - fn try_from( - (item, data): ( - RazorpayRouterData<&types::PaymentsSyncRouterData>, - &Connectors, - ), - ) -> Result { - let ref_id = generate_12_digit_number(); - let auth = RazorpayAuthType::try_from(&item.router_data.connector_auth_type)?; - let merchant_data = JuspayAuthData::try_from(data)?; - let connector_transaction_id = item - .router_data - .request - .connector_transaction_id - .get_connector_transaction_id() - .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; - let connector_request_reference_id = &item.router_data.connector_request_reference_id; - - let second_factor = SecondFactor { - txn_id: connector_request_reference_id.clone(), - id: ref_id.to_string(), - status: SecondFactorStatus::Pending, - version: VERSION, - sf_type: SecondFactorType::Vbv, - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - epg_txn_id: Some(connector_transaction_id.clone()), - ..Default::default() - }; - let order_reference = OrderReference { - id: ref_id.to_string(), - amount: item.amount, - currency: item.router_data.request.currency.to_string(), - status: OrderStatus::PendingAuthentication, - merchant_id: merchant_data.merchant_id.clone(), - order_id: connector_request_reference_id.clone(), - version: VERSION, - order_type: OrderType::OrderPayment, - order_uuid: uuid::Uuid::new_v4().to_string(), - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }; - let txn_mode: TxnMode = match item.router_data.test_mode { - Some(true) | None => TxnMode::Test, - Some(false) => TxnMode::Prod, - }; - let txn_detail = TxnDetail { - order_id: connector_request_reference_id.clone(), - express_checkout: false, - txn_mode, - merchant_id: merchant_data.merchant_id.clone(), - status: TxnStatus::from(item.router_data.status), - net_amount: item.amount, - txn_id: connector_request_reference_id.clone(), - txn_amount: item.amount, - emi_tenure: 0, - txn_uuid: uuid::Uuid::new_v4().to_string(), - id: ref_id.to_string(), - merchant_gateway_account_id: 11476, - txn_type: TxnType::AuthAndSettle, - redirect: true, - version: VERSION, - add_to_locker: false, - currency: item.router_data.request.currency.to_string(), - is_emi: false, - gateway: Gateway::Razorpay, - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }; - - let account_details = AccountDetails { - razorpay_id: auth.razorpay_id.clone(), - razorpay_secret: auth.razorpay_secret, - }; - let merchant_gateway_account = MerchantGatewayAccount { - gateway: Gateway::Razorpay, - disabled: false, - id: ref_id, - account_details: serde_json::to_string(&account_details) - .change_context(errors::ConnectorError::ParsingFailed)?, - test_mode: false, - merchant_id: merchant_data.merchant_id, - ..Default::default() - }; - let gateway_txn_data = GatewayTxnData { - id: ref_id.to_string(), - version: VERSION, - gateway_data: "".to_string(), - gateway_status: "S".to_string(), - match_status: "S".to_string(), - txn_detail_id: ref_id.to_string(), - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - }; Ok(Self { - second_factor, - merchant_gateway_account, - order_reference, - txn_detail, - gateway_txn_data, + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.razorpay_payment_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some(item.response.razorpay_payment_id), + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data }) } } #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RazorpaySyncResponse { - status: PsyncStatus, - is_stateful: bool, - second_factor: SecondFactorResponse, +pub struct WaitScreenData { + display_from_timestamp: i128, + display_to_timestamp: Option, + poll_config: Option, +} + +pub fn get_wait_screen_metadata() -> CustomResult, errors::ConnectorError> +{ + let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos(); + Ok(Some(serde_json::json!(WaitScreenData { + display_from_timestamp: current_time, + display_to_timestamp: Some(current_time + Duration::minutes(5).whole_nanoseconds()), + poll_config: Some(PollConfig { + delay_in_secs: 5, + frequency: 5, + }), + }))) } #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum PsyncStatus { - Charged, - Pending, - Authorizing, +#[serde(rename_all = "camelCase")] +pub struct RazorpaySyncResponse { + id: String, + status: RazorpayStatus, } -impl From for enums::AttemptStatus { - fn from(item: PsyncStatus) -> Self { - match item { - PsyncStatus::Charged => Self::Charged, - PsyncStatus::Pending => Self::Pending, - PsyncStatus::Authorizing => Self::Pending, - } +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum RazorpayStatus { + Created, + Authorized, + Captured, + Refunded, + Failed, +} + +fn get_psync_razorpay_payment_status(razorpay_status: RazorpayStatus) -> enums::AttemptStatus { + match razorpay_status { + RazorpayStatus::Created => enums::AttemptStatus::Pending, + RazorpayStatus::Authorized => enums::AttemptStatus::Authorized, + RazorpayStatus::Captured | RazorpayStatus::Refunded => enums::AttemptStatus::Charged, + RazorpayStatus::Failed => enums::AttemptStatus::Failure, } } @@ -1004,16 +312,14 @@ impl TryFrom, ) -> Result { Ok(Self { - status: enums::AttemptStatus::from(item.response.status), + status: get_psync_razorpay_payment_status(item.response.status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId( - item.response.second_factor.epg_txn_id, - ), + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: Some(item.response.second_factor.txn_id), + connector_response_reference_id: None, incremental_authorization_allowed: None, charges: None, }), @@ -1023,397 +329,310 @@ impl TryFrom>, - gateway: Gateway, - txn_detail_id: u64, - unique_request_id: String, - processed: bool, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum RefundStatus { - Success, - Failure, - #[default] - Pending, -} - -#[derive(Default, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentGatewayResponse { - id: String, - version: i32, -} - -impl - TryFrom<( - &RazorpayRouterData<&types::RefundsRouterData>, - &Connectors, - )> for RazorpayRefundRequest -{ +impl TryFrom<&RazorpayRouterData<&types::RefundsRouterData>> for RazorpayRefundRequest { type Error = error_stack::Report; fn try_from( - (item, data): ( - &RazorpayRouterData<&types::RefundsRouterData>, - &Connectors, - ), + item: &RazorpayRouterData<&types::RefundsRouterData>, ) -> Result { - let ref_id = generate_12_digit_number(); - let auth = RazorpayAuthType::try_from(&item.router_data.connector_auth_type)?; - let merchant_data = JuspayAuthData::try_from(data)?; - let connector_transaction_id = item.router_data.request.connector_transaction_id.clone(); - let connector_request_reference_id = &item.router_data.connector_request_reference_id; - let order_metadata_v2 = OrderMetadataV2 { - id: ref_id.to_string(), - order_reference_id: ref_id.to_string(), - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - ..Default::default() - }; - let second_factor = SecondFactor { - txn_id: connector_request_reference_id.clone(), - id: ref_id.to_string(), - status: SecondFactorStatus::Pending, - version: VERSION, - sf_type: SecondFactorType::Vbv, - date_created: Some(common_utils::date_time::now()), - last_updated: Some(common_utils::date_time::now()), - epg_txn_id: Some(connector_transaction_id.clone()), - ..Default::default() - }; - - let order_reference = OrderReference { - id: ref_id.to_string(), + Ok(Self { amount: item.amount, - currency: item.router_data.request.currency.to_string(), - status: OrderStatus::Success, - merchant_id: merchant_data.merchant_id.clone(), - order_id: connector_request_reference_id.clone(), - version: VERSION, - order_type: OrderType::OrderPayment, - order_uuid: uuid::Uuid::new_v4().to_string(), - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }; - let txn_mode: TxnMode = match item.router_data.test_mode { - Some(true) | None => TxnMode::Test, - Some(false) => TxnMode::Prod, - }; - let txn_detail = TxnDetail { - order_id: connector_request_reference_id.clone(), - express_checkout: false, - txn_mode, - merchant_id: merchant_data.merchant_id.clone(), - status: TxnStatus::from(item.router_data.status), - net_amount: item.amount, - txn_id: connector_request_reference_id.clone(), - txn_amount: item.amount, - emi_tenure: 0, - txn_uuid: uuid::Uuid::new_v4().to_string(), - id: ref_id.to_string(), - merchant_gateway_account_id: 11476, - txn_type: TxnType::AuthAndSettle, - redirect: true, - version: VERSION, - add_to_locker: false, - currency: item.router_data.request.currency.to_string(), - is_emi: false, - gateway: Gateway::Razorpay, - date_created: Some(common_utils::date_time::now()), - last_modified: Some(common_utils::date_time::now()), - ..Default::default() - }; - - let refund = Refund { - id: ref_id, - status: RefundStatus::Pending, - amount: item.amount, - merchant_id: Some(merchant_data.merchant_id.clone()), - gateway: Gateway::Razorpay, - txn_detail_id: ref_id, - unique_request_id: item.router_data.request.refund_id.clone(), - processed: false, - date_created: Some(common_utils::date_time::now()), - }; - let payment_gateway_response = PaymentGatewayResponse { - id: ref_id.to_string(), - version: VERSION, - }; - let payment_source: Secret = - Secret::new("".to_string()); - - let pm = enums::PaymentMethod::Upi; - - let txn_card_info = TxnCardInfo { - txn_detail_id: ref_id.to_string(), - txn_id: item.router_data.connector_request_reference_id.clone(), - payment_method_type: pm.to_string().to_uppercase(), - id: ref_id.to_string(), - payment_method: pm.to_string().to_uppercase(), - payment_source, - date_created: Some(common_utils::date_time::now()), - ..Default::default() - }; - - let account_details = AccountDetails { - razorpay_id: auth.razorpay_id.clone(), - razorpay_secret: auth.razorpay_secret, - }; - - let merchant_gateway_account = MerchantGatewayAccount { - gateway: Gateway::Razorpay, - disabled: false, - id: ref_id, - account_details: serde_json::to_string(&account_details) - .change_context(errors::ConnectorError::ParsingFailed)?, - test_mode: false, - merchant_id: merchant_data.merchant_id, - ..Default::default() - }; - - Ok(Self { - order_metadata_v2, - second_factor, - order_reference, - txn_detail, - refund, - payment_gateway_response, - txn_card_info, - merchant_gateway_account, }) } } -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Success => Self::Success, - RefundStatus::Failure => Self::Failure, - RefundStatus::Pending => Self::Pending, - } - } -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RefundResponse { - txn_id: Option, - refund: RefundRes, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RefundRes { - id: u64, - status: RefundStatus, - amount: FloatMajorUnit, - merchant_id: Option>, - gateway: Gateway, - txn_detail_id: u64, - unique_request_id: String, - epg_txn_id: Option, - response_code: Option, - error_message: Option, - processed: bool, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - date_created: Option, -} - -impl TryFrom> - for types::RefundsRouterData -{ - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - let epg_txn_id = item.response.refund.epg_txn_id.clone(); - let refund_status = enums::RefundStatus::from(item.response.refund.status); - - let response = match epg_txn_id { - Some(epg_txn_id) => Ok(RefundsResponseData { - connector_refund_id: epg_txn_id, - refund_status, - }), - None => Err(hyperswitch_domain_models::router_data::ErrorResponse { - code: item - .response - .refund - .error_message - .clone() - .unwrap_or(NO_ERROR_CODE.to_string()), - message: item - .response - .refund - .response_code - .clone() - .unwrap_or(NO_ERROR_MESSAGE.to_string()), - reason: item.response.refund.response_code.clone(), - status_code: item.http_code, - attempt_status: None, - connector_transaction_id: Some(item.response.refund.unique_request_id.clone()), - network_advice_code: None, - network_decline_code: None, - network_error_message: None, - }), - }; - Ok(Self { - response, - ..item.data - }) - } -} - -impl TryFrom> for types::RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item - .data - .request - .connector_refund_id - .clone() - .ok_or(errors::ConnectorError::MissingConnectorRefundID)?, - refund_status: enums::RefundStatus::from(item.response.refund.status), - }), - ..item.data - }) - } -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -#[serde(untagged)] -pub enum ErrorResponse { - RazorpayErrorResponse(RazorpayErrorResponse), - RazorpayStringError(String), -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub struct RazorpayErrorResponse { - pub code: u16, - pub error_code: Option, - pub status: String, - pub error: bool, - pub error_message: String, - pub error_info: ErrorInfo, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub struct ErrorInfo { - pub code: String, - pub user_message: String, - pub developer_message: String, - pub fields: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub struct Fields { - pub field_name: String, - pub reason: String, -} - -#[derive(Debug, Serialize, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum RazorpayWebhookEventType { - Disabled, -} - #[derive(Debug, Serialize, Deserialize)] -pub struct RazorpayWebhookEvent { - pub payload: RazorpayWebhookPayload, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RazorpayWebhookPayload { - pub refund: Option, - pub payment: RazorpayPaymentWebhookPayload, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RazorpayPaymentWebhookPayload { - pub entity: WebhookPaymentEntity, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RazorpayRefundWebhookPayload { - pub entity: WebhookRefundEntity, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct WebhookRefundEntity { +#[serde(rename_all = "snake_case")] +pub struct RazorpayRefundResponse { pub id: String, pub status: RazorpayRefundStatus, } -#[derive(Debug, Serialize, Deserialize)] -pub struct WebhookPaymentEntity { - pub id: String, - pub status: RazorpayPaymentStatus, -} - -#[derive(Debug, Serialize, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum RazorpayPaymentStatus { - Created, - Authorized, - Captured, - Failed, - Refunded, -} - #[derive(Debug, Serialize, Eq, PartialEq, Deserialize)] #[serde(rename_all = "lowercase")] pub enum RazorpayRefundStatus { - Pending, + Created, Processed, Failed, + Pending, } -impl TryFrom for api_models::webhooks::IncomingWebhookEvent { - type Error = errors::ConnectorError; - fn try_from(webhook_payload: RazorpayWebhookPayload) -> Result { - webhook_payload - .refund - .map_or( - match webhook_payload.payment.entity.status { - RazorpayPaymentStatus::Created => Some(Self::PaymentIntentProcessing), - RazorpayPaymentStatus::Authorized => { - Some(Self::PaymentIntentAuthorizationSuccess) - } - RazorpayPaymentStatus::Captured => Some(Self::PaymentIntentSuccess), - RazorpayPaymentStatus::Failed => Some(Self::PaymentIntentFailure), - RazorpayPaymentStatus::Refunded => None, - }, - |refund_data| match refund_data.entity.status { - RazorpayRefundStatus::Pending => None, - RazorpayRefundStatus::Processed => Some(Self::RefundSuccess), - RazorpayRefundStatus::Failed => Some(Self::RefundFailure), - }, - ) - .ok_or(errors::ConnectorError::WebhookEventTypeNotFound) +impl From for enums::RefundStatus { + fn from(item: RazorpayRefundStatus) -> Self { + match item { + RazorpayRefundStatus::Processed => Self::Success, + RazorpayRefundStatus::Pending | RazorpayRefundStatus::Created => Self::Pending, + RazorpayRefundStatus::Failed => Self::Failure, + } } } + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +// This code can be used later when Razorpay webhooks are implemented + +// #[derive(Debug, Deserialize, Serialize)] +// #[serde(untagged)] +// pub enum RazorpayPaymentsResponseData { +// PsyncResponse(RazorpaySyncResponse), +// WebhookResponse(WebhookPaymentEntity), +// } + +// impl From for enums::AttemptStatus { +// fn from(status: RazorpayWebhookPaymentStatus) -> Self { +// match status { +// RazorpayWebhookPaymentStatus::Authorized => Self::Authorized, +// RazorpayWebhookPaymentStatus::Captured => Self::Charged, +// RazorpayWebhookPaymentStatus::Failed => Self::Failure, +// } +// } +// } + +// impl TryFrom> +// for RouterData +// { +// type Error = error_stack::Report; +// fn try_from( +// item: ResponseRouterData, +// ) -> Result { +// match item.response { +// RazorpayPaymentsResponseData::PsyncResponse(sync_response) => { +// let status = get_psync_razorpay_payment_status(sync_response.status.clone()); +// Ok(Self { +// status, +// response: if is_payment_failure(status) { +// Err(RouterErrorResponse { +// code: sync_response.status.clone().to_string(), +// message: sync_response.status.clone().to_string(), +// reason: Some(sync_response.status.to_string()), +// status_code: item.http_code, +// attempt_status: Some(status), +// connector_transaction_id: None, +// network_advice_code: None, +// network_decline_code: None, +// network_error_message: None, +// }) +// } else { +// Ok(PaymentsResponseData::TransactionResponse { +// resource_id: ResponseId::NoResponseId, +// 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, +// charges: None, +// }) +// }, +// ..item.data +// }) +// } +// RazorpayPaymentsResponseData::WebhookResponse(webhook_payment_entity) => { +// let razorpay_status = webhook_payment_entity.status; +// let status = enums::AttemptStatus::from(razorpay_status.clone()); + +// Ok(Self { +// status, +// response: if is_payment_failure(status) { +// Err(RouterErrorResponse { +// code: razorpay_status.clone().to_string(), +// message: razorpay_status.clone().to_string(), +// reason: Some(razorpay_status.to_string()), +// status_code: item.http_code, +// attempt_status: Some(status), +// connector_transaction_id: Some(webhook_payment_entity.id.clone()), +// network_advice_code: None, +// network_decline_code: None, +// network_error_message: None, +// }) +// } else { +// Ok(PaymentsResponseData::TransactionResponse { +// resource_id: ResponseId::ConnectorTransactionId( +// webhook_payment_entity.id.clone(), +// ), +// redirection_data: Box::new(None), +// mandate_reference: Box::new(None), +// connector_metadata: None, +// network_txn_id: None, +// connector_response_reference_id: Some(webhook_payment_entity.id), +// incremental_authorization_allowed: None, +// charges: None, +// }) +// }, +// ..item.data +// }) +// } +// } +// } +// } + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ErrorResponse { + RazorpayErrorResponse(RazorpayErrorResponse), + RazorpayStringError(String), + RazorpayError(RazorpayErrorMessage), +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct RazorpayErrorMessage { + pub message: String, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct RazorpayErrorResponse { + pub error: RazorpayError, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct RazorpayError { + pub code: String, + pub description: String, + pub source: Option, + pub step: Option, + pub reason: Option, + pub metadata: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct Metadata { + pub order_id: Option, +} + +// This code can be used later when Razorpay webhooks are implemented + +// #[derive(Debug, Serialize, Deserialize)] + +// pub struct RazorpayWebhookPayload { +// pub event: RazorpayWebhookEventType, +// pub payload: RazorpayWebhookPayloadBody, +// } + +// #[derive(Debug, Serialize, Deserialize)] +// #[serde(untagged)] +// pub enum RazorpayWebhookEventType { +// Payments(RazorpayWebhookPaymentEvent), +// Refunds(RazorpayWebhookRefundEvent), +// } + +// #[derive(Debug, Serialize, Deserialize)] +// pub struct RazorpayWebhookPayloadBody { +// pub refund: Option, +// pub payment: RazorpayPaymentWebhookPayload, +// } + +// #[derive(Debug, Serialize, Deserialize)] +// pub struct RazorpayPaymentWebhookPayload { +// pub entity: WebhookPaymentEntity, +// } + +// #[derive(Debug, Serialize, Deserialize)] +// pub struct RazorpayRefundWebhookPayload { +// pub entity: WebhookRefundEntity, +// } + +// #[derive(Debug, Serialize, Deserialize)] +// pub struct WebhookRefundEntity { +// pub id: String, +// pub status: RazorpayWebhookRefundEvent, +// } + +// #[derive(Debug, Serialize, Eq, PartialEq, Deserialize)] +// pub enum RazorpayWebhookRefundEvent { +// #[serde(rename = "refund.created")] +// Created, +// #[serde(rename = "refund.processed")] +// Processed, +// #[serde(rename = "refund.failed")] +// Failed, +// #[serde(rename = "refund.speed_change")] +// SpeedChange, +// } + +// #[derive(Debug, Serialize, Deserialize)] +// pub struct WebhookPaymentEntity { +// pub id: String, +// pub status: RazorpayWebhookPaymentStatus, +// } + +// #[derive(Debug, Serialize, Eq, PartialEq, Clone, Deserialize)] +// #[serde(rename_all = "snake_case")] +// pub enum RazorpayWebhookPaymentStatus { +// Authorized, +// Captured, +// Failed, +// } + +// #[derive(Debug, Serialize, Eq, PartialEq, Deserialize)] +// pub enum RazorpayWebhookPaymentEvent { +// #[serde(rename = "payment.authorized")] +// Authorized, +// #[serde(rename = "payment.captured")] +// Captured, +// #[serde(rename = "payment.failed")] +// Failed, +// } + +// impl TryFrom for api_models::webhooks::IncomingWebhookEvent { +// type Error = errors::ConnectorError; + +// fn try_from(event_type: RazorpayWebhookEventType) -> Result { +// match event_type { +// RazorpayWebhookEventType::Payments(payment_event) => match payment_event { +// RazorpayWebhookPaymentEvent::Authorized => { +// Ok(Self::PaymentIntentAuthorizationSuccess) +// } +// RazorpayWebhookPaymentEvent::Captured => Ok(Self::PaymentIntentSuccess), +// RazorpayWebhookPaymentEvent::Failed => Ok(Self::PaymentIntentFailure), +// }, +// RazorpayWebhookEventType::Refunds(refund_event) => match refund_event { +// RazorpayWebhookRefundEvent::Processed => Ok(Self::RefundSuccess), +// RazorpayWebhookRefundEvent::Created => Ok(Self::RefundSuccess), +// RazorpayWebhookRefundEvent::Failed => Ok(Self::RefundFailure), +// RazorpayWebhookRefundEvent::SpeedChange => Ok(Self::EventNotSupported), +// }, +// } +// } +// } diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 4195b155d1..7c46a8ffc0 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -38,8 +38,8 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, AuthorizeSessionToken, CalculateTax, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PostProcessing, PostSessionTokens, - PreProcessing, Reject, SdkSessionUpdate, UpdateMetadata, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PostProcessing, + PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, UpdateMetadata, }, webhooks::VerifyWebhookSource, Authenticate, AuthenticationConfirmation, ExternalVaultCreateFlow, ExternalVaultDeleteFlow, @@ -53,12 +53,12 @@ use hyperswitch_domain_models::{ UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, - ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, - PaymentsApproveData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RetrieveFileRequestData, - SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, UploadFileRequestData, - VaultRequestData, VerifyWebhookSourceRequestData, + ConnectorCustomerData, CreateOrderRequestData, DefendDisputeRequestData, + MandateRevokeRequestData, PaymentsApproveData, PaymentsIncrementalAuthorizationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, + UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, AuthenticationResponseData, DefendDisputeResponse, @@ -92,7 +92,7 @@ use hyperswitch_interfaces::{ ConnectorCustomer, PaymentApprove, PaymentAuthorizeSessionToken, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSessionUpdate, PaymentUpdateMetadata, PaymentsCompleteAuthorize, - PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, + PaymentsCreateOrder, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }, revenue_recovery::RevenueRecovery, vault::{ @@ -589,6 +589,127 @@ default_imp_for_post_session_tokens!( connectors::CtpMastercard ); +macro_rules! default_imp_for_create_order { + ($($path:ident::$connector:ident),*) => { + $( impl PaymentsCreateOrder for $path::$connector {} + impl + ConnectorIntegration< + CreateOrder, + CreateOrderRequestData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_create_order!( + connectors::Aci, + connectors::Adyen, + connectors::Adyenplatform, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Archipel, + connectors::Authorizedotnet, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Barclaycard, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Braintree, + connectors::Boku, + connectors::Billwerk, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Checkout, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::Cybersource, + connectors::Datatrans, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Ebanx, + connectors::Elavon, + connectors::Facilitapay, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Forte, + connectors::Getnet, + connectors::Helcim, + connectors::HyperswitchVault, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Juspaythreedsserver, + connectors::Klarna, + connectors::Rapyd, + connectors::Recurly, + connectors::Redsys, + connectors::Riskified, + connectors::Shift4, + connectors::Signifyd, + connectors::Square, + connectors::Stax, + connectors::Stripe, + connectors::Stripebilling, + connectors::Taxjar, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Multisafepay, + connectors::Netcetera, + connectors::Nmi, + connectors::Nomupay, + connectors::Noon, + connectors::Nordea, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Opayo, + connectors::Opennode, + connectors::Nuvei, + connectors::Paybox, + connectors::Payeezy, + connectors::Payme, + connectors::Payone, + connectors::Paypal, + connectors::Paystack, + connectors::Payu, + connectors::Placetopay, + connectors::Plaid, + connectors::Fiuu, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Gpayments, + connectors::Hipay, + connectors::Wise, + connectors::Worldline, + connectors::Worldpay, + connectors::Worldpayxml, + connectors::Worldpayvantiv, + connectors::Wellsfargo, + connectors::Wellsfargopayout, + connectors::Xendit, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Threedsecureio, + connectors::Thunes, + connectors::Tokenio, + connectors::Trustpay, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Deutschebank, + connectors::Vgs, + connectors::Volt, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + macro_rules! default_imp_for_update_metadata { ($($path:ident::$connector:ident),*) => { $( impl PaymentUpdateMetadata for $path::$connector {} diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 952de0b231..5786463770 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -17,9 +17,9 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PSync, + PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, revenue_recovery::{ @@ -36,9 +36,9 @@ use hyperswitch_domain_models::{ RevenueRecoveryRecordBackRequest, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, - CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, - MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsApproveData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, + DefendDisputeRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, + PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, @@ -97,11 +97,11 @@ use hyperswitch_interfaces::{ files_v2::{FileUploadV2, RetrieveFileV2, UploadFileV2}, payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, - PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, - PaymentSyncV2, PaymentTokenV2, PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, - TaxCalculationV2, + PaymentAuthorizeV2, PaymentCaptureV2, PaymentCreateOrderV2, + PaymentIncrementalAuthorizationV2, PaymentPostSessionTokensV2, PaymentRejectV2, + PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, + PaymentUpdateMetadataV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, revenue_recovery_v2::{ @@ -142,6 +142,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl PaymentSessionUpdateV2 for $path::$connector{} impl PaymentPostSessionTokensV2 for $path::$connector{} impl PaymentUpdateMetadataV2 for $path::$connector{} + impl PaymentCreateOrderV2 for $path::$connector{} impl ExternalVaultV2 for $path::$connector{} impl ConnectorIntegrationV2 @@ -241,6 +242,8 @@ macro_rules! default_imp_for_new_connector_integration_payment { PaymentsUpdateMetadataData, PaymentsResponseData, > for $path::$connector{} + impl ConnectorIntegrationV2 + for $path::$connector{} )* }; } diff --git a/crates/hyperswitch_connectors/src/types.rs b/crates/hyperswitch_connectors/src/types.rs index f6673e7dc4..0160457d2f 100644 --- a/crates/hyperswitch_connectors/src/types.rs +++ b/crates/hyperswitch_connectors/src/types.rs @@ -8,18 +8,19 @@ use hyperswitch_domain_models::{ authentication::{ Authentication, PostAuthentication, PreAuthentication, PreAuthenticationVersionCall, }, - Accept, AccessTokenAuth, Authorize, Capture, Defend, Evidence, PSync, PostProcessing, - PreProcessing, Retrieve, Session, Upload, Void, + Accept, AccessTokenAuth, Authorize, Capture, CreateOrder, Defend, Evidence, PSync, + PostProcessing, PreProcessing, Retrieve, Session, Upload, Void, }, router_request_types::{ authentication::{ ConnectorAuthenticationRequestData, ConnectorPostAuthenticationRequestData, PreAuthNRequestData, }, - AcceptDisputeRequestData, AccessTokenRequestData, DefendDisputeRequestData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPostProcessingData, - PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, - RetrieveFileRequestData, SubmitEvidenceRequestData, UploadFileRequestData, + AcceptDisputeRequestData, AccessTokenRequestData, CreateOrderRequestData, + DefendDisputeRequestData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, RetrieveFileRequestData, SubmitEvidenceRequestData, + UploadFileRequestData, }, router_response_types::{ AcceptDisputeResponse, AuthenticationResponseData, DefendDisputeResponse, @@ -55,6 +56,8 @@ pub(crate) type PaymentsPreprocessingResponseRouterData = ResponseRouterData; pub(crate) type PaymentsSessionResponseRouterData = ResponseRouterData; +pub(crate) type CreateOrderResponseRouterData = + ResponseRouterData; pub(crate) type AcceptDisputeRouterData = RouterData; diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 091e3efd88..ca59dd6365 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1685,6 +1685,7 @@ pub trait PaymentsAuthorizeRequestData { fn get_connector_mandate_id(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn get_ip_address_as_optional(&self) -> Option>; + fn get_optional_user_agent(&self) -> Option; fn get_original_amount(&self) -> i64; fn get_surcharge_amount(&self) -> Option; fn get_tax_on_surcharge_amount(&self) -> Option; @@ -1703,6 +1704,7 @@ pub trait PaymentsAuthorizeRequestData { &self, ) -> Result; fn get_connector_testing_data(&self) -> Option; + fn get_order_id(&self) -> Result; } impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { @@ -1804,6 +1806,11 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .map(|ip| Secret::new(ip.to_string())) }) } + fn get_optional_user_agent(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.user_agent) + } fn get_original_amount(&self) -> i64 { self.surcharge_details .as_ref() @@ -1926,6 +1933,12 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { fn get_connector_testing_data(&self) -> Option { self.connector_testing_data.clone() } + + fn get_order_id(&self) -> Result { + self.order_id + .to_owned() + .ok_or(errors::ConnectorError::RequestEncodingFailed) + } } pub trait PaymentsCaptureRequestData { @@ -6077,6 +6090,7 @@ pub(crate) fn convert_setup_mandate_router_data_to_authorize_router_data( merchant_account_id: None, merchant_config_currency: None, connector_testing_data: data.request.connector_testing_data.clone(), + order_id: None, } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index fb655280fe..12de76ed77 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -469,6 +469,7 @@ pub struct PaymentAttempt { pub processor_merchant_id: id_type::MerchantId, /// merchantwho invoked the resource based api (identifier) and through what source (Api, Jwt(Dashboard)) pub created_by: Option, + pub connector_request_reference_id: Option, } impl PaymentAttempt { @@ -607,6 +608,7 @@ impl PaymentAttempt { feature_metadata: None, processor_merchant_id: payment_intent.merchant_id.clone(), created_by: None, + connector_request_reference_id: None, }) } @@ -696,6 +698,7 @@ impl PaymentAttempt { card_discovery: None, processor_merchant_id: payment_intent.merchant_id.clone(), created_by: None, + connector_request_reference_id: None, }) } @@ -795,6 +798,7 @@ impl PaymentAttempt { charges: None, processor_merchant_id: payment_intent.merchant_id.clone(), created_by: None, + connector_request_reference_id: None, }) } @@ -1755,6 +1759,7 @@ pub enum PaymentAttemptUpdate { connector: String, merchant_connector_id: id_type::MerchantConnectorAccountId, authentication_type: storage_enums::AuthenticationType, + connector_request_reference_id: Option, }, /// Update the payment attempt on confirming the intent, before calling the connector, when payment_method_id is present ConfirmIntentTokenized { @@ -2160,6 +2165,7 @@ impl behaviour::Conversion for PaymentAttempt { feature_metadata, processor_merchant_id, created_by, + connector_request_reference_id, } = self; let AttemptAmountDetails { @@ -2254,6 +2260,7 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|details| details.network_error_message.clone()), processor_merchant_id: Some(processor_merchant_id), created_by: created_by.map(|cb| cb.to_string()), + connector_request_reference_id, }) } @@ -2376,6 +2383,7 @@ impl behaviour::Conversion for PaymentAttempt { created_by: storage_model .created_by .and_then(|created_by| created_by.parse::().ok()), + connector_request_reference_id: storage_model.connector_request_reference_id, }) } .await @@ -2434,6 +2442,7 @@ impl behaviour::Conversion for PaymentAttempt { feature_metadata, processor_merchant_id, created_by, + connector_request_reference_id, } = self; let card_network = payment_method_data @@ -2525,6 +2534,7 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|details| details.network_error_message.clone()), processor_merchant_id: Some(processor_merchant_id), created_by: created_by.map(|cb| cb.to_string()), + connector_request_reference_id, }) } } @@ -2539,6 +2549,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector, merchant_connector_id, authentication_type, + connector_request_reference_id, } => Self { status: Some(status), payment_method_id: None, @@ -2563,6 +2574,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id, }, PaymentAttemptUpdate::ErrorUpdate { status, @@ -2594,6 +2606,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: error.network_advice_code, network_decline_code: error.network_decline_code, network_error_message: error.network_error_message, + connector_request_reference_id: None, }, PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => { let ConfirmIntentResponseUpdate { @@ -2630,6 +2643,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id: None, } } PaymentAttemptUpdate::SyncUpdate { @@ -2660,6 +2674,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id: None, }, PaymentAttemptUpdate::CaptureUpdate { status, @@ -2689,6 +2704,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id: None, }, PaymentAttemptUpdate::PreCaptureUpdate { amount_to_capture, @@ -2717,6 +2733,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id: None, }, PaymentAttemptUpdate::ConfirmIntentTokenized { status, @@ -2749,6 +2766,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal network_advice_code: None, network_decline_code: None, network_error_message: None, + connector_request_reference_id: None, }, } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index d156243b87..2f2ed98f1a 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -632,6 +632,9 @@ impl } => { todo!() } + router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse { + .. + } => todo!(), }, Err(ref error_response) => { let ErrorResponse { @@ -845,6 +848,9 @@ impl } => { todo!() } + router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse { + .. + } => todo!(), }, Err(ref error_response) => { let ErrorResponse { @@ -1076,6 +1082,9 @@ impl } => { todo!() } + router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse { + .. + } => todo!(), }, Err(ref error_response) => { let ErrorResponse { @@ -1331,6 +1340,9 @@ impl } => { todo!() } + router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse { + .. + } => todo!(), }, Err(ref error_response) => { let ErrorResponse { diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs index 5cf47bf62a..f139249f62 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -73,3 +73,6 @@ pub struct RecordAttempt; #[derive(Debug, Clone)] pub struct UpdateMetadata; + +#[derive(Debug, Clone)] +pub struct CreateOrder; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index da2ce5c933..8dfda36836 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -78,6 +78,7 @@ pub struct PaymentsAuthorizeData { pub merchant_account_id: Option>, pub merchant_config_currency: Option, pub connector_testing_data: Option, + pub order_id: Option, } #[derive(Debug, Clone)] pub struct PaymentsPostSessionTokensData { @@ -315,6 +316,23 @@ impl TryFrom for PaymentMethodTokenizationData { } } +#[derive(Debug, Clone)] +pub struct CreateOrderRequestData { + pub minor_amount: MinorUnit, + pub currency: storage_enums::Currency, +} + +impl TryFrom for CreateOrderRequestData { + type Error = error_stack::Report; + + fn try_from(data: PaymentsAuthorizeData) -> Result { + Ok(Self { + minor_amount: data.minor_amount, + currency: data.currency, + }) + } +} + #[derive(Debug, Clone)] pub struct PaymentsPreProcessingData { pub payment_method_data: Option, diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 516682889c..dc9d11b032 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -77,6 +77,9 @@ pub enum PaymentsResponseData { PaymentResourceUpdateResponse { status: common_enums::PaymentResourceUpdateStatus, }, + PaymentsCreateOrderResponse { + order_id: String, + }, } #[derive(Debug, Clone)] diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index dcb57fdff3..1e7b6f2318 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -7,9 +7,10 @@ use crate::{ mandate_revoke::MandateRevoke, revenue_recovery::RecoveryRecordBack, AccessTokenAuth, Authenticate, AuthenticationConfirmation, Authorize, AuthorizeSessionToken, BillingConnectorInvoiceSync, BillingConnectorPaymentsSync, CalculateTax, Capture, - CompleteAuthorize, CreateConnectorCustomer, Execute, IncrementalAuthorization, PSync, - PaymentMethodToken, PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, - RSync, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, VerifyWebhookSource, Void, + CompleteAuthorize, CreateConnectorCustomer, CreateOrder, Execute, IncrementalAuthorization, + PSync, PaymentMethodToken, PostAuthenticate, PostSessionTokens, PreAuthenticate, + PreProcessing, RSync, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, + VerifyWebhookSource, Void, }, router_request_types::{ revenue_recovery::{ @@ -22,9 +23,9 @@ use crate::{ UasPreAuthenticationRequestData, }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, - ConnectorCustomerData, MandateRevokeRequestData, PaymentMethodTokenizationData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, - PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, + ConnectorCustomerData, CreateOrderRequestData, MandateRevokeRequestData, + PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, VaultRequestData, @@ -71,6 +72,8 @@ pub type PaymentsSessionRouterData = RouterData; +pub type CreateOrderRouterData = + RouterData; pub type UasPostAuthenticationRouterData = RouterData; pub type UasPreAuthenticationRouterData = diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index 922094f276..82da16a541 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -382,6 +382,20 @@ pub trait ConnectorSpecifications { fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { None } + + #[cfg(feature = "v2")] + /// Generate connector request reference ID + fn generate_connector_request_reference_id( + &self, + payment_intent: &hyperswitch_domain_models::payments::PaymentIntent, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + ) -> String { + payment_intent + .merchant_reference_id + .as_ref() + .map(|id| id.get_string_repr().to_owned()) + .unwrap_or_else(|| payment_attempt.id.get_string_repr().to_owned()) + } } /// Extended trait for connector common to allow functions with generic type diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index 01e7d2aedf..8b017be37d 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -1,18 +1,22 @@ //! Payments interface use hyperswitch_domain_models::{ - router_flow_types::payments::{ - Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + router_flow_types::{ + payments::{ + Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, + CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, UpdateMetadata, Void, + }, + CreateOrder, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, - PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, - PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + CreateOrderRequestData, PaymentMethodTokenizationData, PaymentsApproveData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, @@ -43,6 +47,7 @@ pub trait Payment: + PaymentSessionUpdate + PaymentPostSessionTokens + PaymentUpdateMetadata + + PaymentsCreateOrder { } @@ -163,3 +168,9 @@ pub trait PaymentsPostProcessing: api::ConnectorIntegration { } + +/// trait PaymentsCreateOrder +pub trait PaymentsCreateOrder: + api::ConnectorIntegration +{ +} diff --git a/crates/hyperswitch_interfaces/src/api/payments_v2.rs b/crates/hyperswitch_interfaces/src/api/payments_v2.rs index f60925435e..dbc7b36479 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -4,16 +4,17 @@ use hyperswitch_domain_models::{ router_data_v2::PaymentFlowData, router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, - PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, - PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + CreateOrderRequestData, PaymentMethodTokenizationData, PaymentsApproveData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, @@ -126,6 +127,12 @@ pub trait PaymentPostSessionTokensV2: { } +/// trait ConnectorCreateOrderV2 +pub trait PaymentCreateOrderV2: + ConnectorIntegrationV2 +{ +} + /// trait PaymentUpdateMetadataV2 pub trait PaymentUpdateMetadataV2: ConnectorIntegrationV2< @@ -216,5 +223,6 @@ pub trait PaymentV2: + PaymentSessionUpdateV2 + PaymentPostSessionTokensV2 + PaymentUpdateMetadataV2 + + PaymentCreateOrderV2 { } diff --git a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs index 8122f6365f..3bab4b3666 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs @@ -518,6 +518,23 @@ impl ConnectorSpecifications for ConnectorEnum { Self::New(connector) => connector.get_connector_about(), } } + + #[cfg(feature = "v2")] + /// Generate connector request reference ID + fn generate_connector_request_reference_id( + &self, + payment_intent: &hyperswitch_domain_models::payments::PaymentIntent, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + ) -> String { + match self { + Self::Old(connector) => { + connector.generate_connector_request_reference_id(payment_intent, payment_attempt) + } + Self::New(connector) => { + connector.generate_connector_request_reference_id(payment_intent, payment_attempt) + } + } + } } impl ConnectorCommon for ConnectorEnum { diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index e19580a85b..d2f90998c8 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -10,7 +10,7 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, @@ -37,14 +37,15 @@ use hyperswitch_domain_models::{ UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, - CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, - MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, - PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, - UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, + CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData, + DefendDisputeRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VaultRequestData, + VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -103,6 +104,9 @@ pub type SetupMandateType = /// Type alias for `ConnectorIntegration` pub type MandateRevokeType = dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type CreateOrderType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type PaymentsPreProcessingType = dyn ConnectorIntegration; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 4f8b96d5fb..171f37cca1 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -565,6 +565,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ThreeDsData, api_models::payments::ThreeDsMethodData, api_models::payments::PollConfigResponse, + api_models::payments::PollConfig, api_models::payments::ExternalAuthenticationDetailsResponse, api_models::payments::ExtendedCardInfo, api_models::payment_methods::RequiredFieldInfo, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 87615691f0..9a624c12a9 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -537,6 +537,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ThreeDsData, api_models::payments::ThreeDsMethodData, api_models::payments::PollConfigResponse, + api_models::payments::PollConfig, api_models::payments::ExternalAuthenticationDetailsResponse, api_models::payments::ExtendedCardInfo, api_models::payments::PaymentsConfirmIntentRequest, diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs index 478c2ea244..fb516c9725 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -1061,7 +1061,16 @@ impl Default for RequiredFields { enums::PaymentMethodType::UpiCollect, connectors(vec![( Connector::Razorpay, - fields(vec![], vec![], vec![RequiredField::UpiCollectVpaId]), + fields( + vec![], + vec![], + vec![ + RequiredField::UpiCollectVpaId, + RequiredField::BillingEmail, + RequiredField::BillingPhone, + RequiredField::BillingPhoneCountryCode, + ], + ), )]), )])), ), diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 0dd814bc03..c2d2b33b28 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -833,6 +833,7 @@ pub enum StripeNextAction { WaitScreenInformation { display_from_timestamp: i128, display_to_timestamp: Option, + poll_config: Option, }, InvokeSdkClient { next_action_data: payments::SdkNextActionData, @@ -897,9 +898,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::WaitScreenInformation { display_from_timestamp, display_to_timestamp, + poll_config: _, } => StripeNextAction::WaitScreenInformation { display_from_timestamp, display_to_timestamp, + poll_config: None, }, payments::NextActionData::ThreeDsInvoke { .. } => StripeNextAction::RedirectToUrl { redirect_to_url: RedirectUrl { diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 40e0e7ae6c..3cf996685c 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -386,6 +386,7 @@ pub enum StripeNextAction { WaitScreenInformation { display_from_timestamp: i128, display_to_timestamp: Option, + poll_config: Option, }, InvokeSdkClient { next_action_data: payments::SdkNextActionData, @@ -450,9 +451,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::WaitScreenInformation { display_from_timestamp, display_to_timestamp, + poll_config: _, } => StripeNextAction::WaitScreenInformation { display_from_timestamp, display_to_timestamp, + poll_config: None, }, payments::NextActionData::ThreeDsInvoke { .. } => StripeNextAction::RedirectToUrl { redirect_to_url: RedirectUrl { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index cd89c96748..d72ef0410b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3323,6 +3323,10 @@ where &call_connector_action, ); + let should_continue_further = router_data + .create_order_at_connector(state, &connector, should_continue_further) + .await?; + let updated_customer = call_create_connector_customer_if_required( state, customer, @@ -3505,6 +3509,17 @@ where id: merchant_connector_id.get_string_repr().to_owned(), })?; + operation + .to_domain()? + .populate_payment_data( + state, + payment_data, + merchant_context, + business_profile, + &connector, + ) + .await?; + let updated_customer = call_create_connector_customer_if_required( state, customer, @@ -3543,6 +3558,10 @@ where &call_connector_action, ); + let should_continue_further = router_data + .create_order_at_connector(state, &connector, should_continue_further) + .await?; + // In case of authorize flow, pre-task and post-tasks are being called in build request // if we do not want to proceed further, then the function will return Ok(None, false) let (connector_request, should_continue_further) = if should_continue_further { @@ -8610,6 +8629,9 @@ pub trait OperationSessionSetters { #[cfg(feature = "v1")] fn set_vault_operation(&mut self, vault_operation: domain_payments::VaultOperation); + #[cfg(feature = "v2")] + fn set_connector_request_reference_id(&mut self, reference_id: Option); + #[cfg(feature = "v2")] fn set_vault_session_details( &mut self, @@ -9180,6 +9202,10 @@ impl OperationSessionSetters for PaymentIntentData { todo!() } + fn set_connector_request_reference_id(&mut self, reference_id: Option) { + todo!() + } + fn set_vault_session_details( &mut self, vault_session_details: Option, @@ -9455,6 +9481,10 @@ impl OperationSessionSetters for PaymentConfirmData { self.payment_attempt.connector = connector; } + fn set_connector_request_reference_id(&mut self, reference_id: Option) { + self.payment_attempt.connector_request_reference_id = reference_id; + } + fn set_vault_session_details( &mut self, external_vault_session_details: Option, @@ -9726,6 +9756,10 @@ impl OperationSessionSetters for PaymentStatusData { todo!() } + fn set_connector_request_reference_id(&mut self, reference_id: Option) { + todo!() + } + fn set_vault_session_details( &mut self, external_vault_session_details: Option, @@ -9998,6 +10032,10 @@ impl OperationSessionSetters for PaymentCaptureData { todo!() } + fn set_connector_request_reference_id(&mut self, reference_id: Option) { + todo!() + } + fn set_vault_session_details( &mut self, external_vault_session_details: Option, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index a639805cde..38ecb041f8 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -199,6 +199,20 @@ pub trait Feature { ) -> RouterResult<(Option, bool)> { Ok((None, true)) } + + async fn create_order_at_connector( + &mut self, + _state: &SessionState, + _connector: &api::ConnectorData, + should_continue_payment: bool, + ) -> RouterResult + where + F: Clone, + Self: Sized, + dyn api::Connector: services::ConnectorIntegration, + { + Ok(should_continue_payment) + } } #[cfg(feature = "dummy_connector")] @@ -643,6 +657,18 @@ impl { } +#[cfg(feature = "dummy_connector")] +impl api::PaymentsCreateOrder for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::CreateOrder, + types::CreateOrderRequestData, + types::PaymentsResponseData, + > for connector::DummyConnector +{ +} + #[cfg(feature = "dummy_connector")] impl api::PaymentUpdateMetadata for connector::DummyConnector {} #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 8c4a7a585b..2cd42f39e6 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -415,6 +415,24 @@ impl Feature for types::PaymentsAu _ => Ok((None, true)), } } + + async fn create_order_at_connector( + &mut self, + state: &SessionState, + connector: &api::ConnectorData, + should_continue_payment: bool, + ) -> RouterResult { + let create_order_result = + create_order_at_connector(self, state, connector, should_continue_payment).await?; + + let should_continue_payment = update_router_data_with_create_order_result( + create_order_result, + self, + should_continue_payment, + )?; + + Ok(should_continue_payment) + } } pub trait RouterDataAuthorize { @@ -700,3 +718,103 @@ async fn process_capture_flow( router_data.response = Ok(updated_response); Ok(router_data) } + +async fn create_order_at_connector( + router_data: &mut types::RouterData< + F, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + state: &SessionState, + connector: &api::ConnectorData, + should_continue_payment: bool, +) -> RouterResult { + if connector + .connector_name + .requires_order_creation_before_payment(router_data.payment_method) + && should_continue_payment + { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::CreateOrder, + types::CreateOrderRequestData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let request_data = types::CreateOrderRequestData::try_from(router_data.request.clone())?; + + let response_data: Result = + Err(types::ErrorResponse::default()); + + let createorder_router_data = + helpers::router_data_type_conversion::<_, api::CreateOrder, _, _, _, _>( + router_data.clone(), + request_data, + response_data, + ); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &createorder_router_data, + payments::CallConnectorAction::Trigger, + None, + None, + ) + .await + .to_payment_failed_response()?; + + let create_order_resp = match resp.response { + Ok(res) => { + if let types::PaymentsResponseData::PaymentsCreateOrderResponse { order_id } = res { + Ok(Some(order_id)) + } else { + Err(error_stack::report!(ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "Unexpected response format from connector: {:?}", + res + )))? + } + } + Err(error) => Err(error), + }; + + Ok(types::CreateOrderResult { + create_order_result: create_order_resp, + is_create_order_performed: true, + }) + } else { + Ok(types::CreateOrderResult { + create_order_result: Ok(None), + is_create_order_performed: false, + }) + } +} + +fn update_router_data_with_create_order_result( + create_order_result: types::CreateOrderResult, + router_data: &mut types::RouterData< + F, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + should_continue_further: bool, +) -> RouterResult { + if create_order_result.is_create_order_performed { + match create_order_result.create_order_result { + Ok(Some(order_id)) => { + router_data.request.order_id = Some(order_id.clone()); + router_data.response = + Ok(types::PaymentsResponseData::PaymentsCreateOrderResponse { order_id }); + Ok(true) + } + Ok(None) => Err(error_stack::report!(ApiErrorResponse::InternalServerError) + .attach_printable("Order Id not found."))?, + Err(err) => { + router_data.response = Err(err.clone()); + Ok(false) + } + } + } else { + Ok(should_continue_further) + } +} diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 83cffbf419..94ed262473 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use common_utils::{ext_traits::Encode, fp_utils::when, types::keymanager::ToEncryptable}; use error_stack::ResultExt; use hyperswitch_domain_models::payments::PaymentConfirmData; +use hyperswitch_interfaces::api::ConnectorSpecifications; use masking::{ExposeOptionInterface, PeekInterface}; use router_env::{instrument, tracing}; @@ -21,7 +22,7 @@ use crate::{ utils as core_utils, }, routes::{app::ReqState, SessionState}, - services, + services::{self, connector_integration_interface::ConnectorEnum}, types::{ self, api::{self, ConnectorCallType, PaymentIdTypeExt}, @@ -368,6 +369,25 @@ impl Domain( + &'a self, + state: &SessionState, + payment_data: &mut PaymentConfirmData, + _merchant_context: &domain::MerchantContext, + business_profile: &domain::Profile, + connector_data: &api::ConnectorData, + ) -> CustomResult<(), errors::ApiErrorResponse> { + let connector_request_reference_id = connector_data + .connector + .generate_connector_request_reference_id( + &payment_data.payment_intent, + &payment_data.payment_attempt, + ); + payment_data.set_connector_request_reference_id(Some(connector_request_reference_id)); + Ok(()) + } + #[cfg(feature = "v2")] async fn create_or_fetch_payment_method<'a>( &'a self, @@ -539,6 +559,11 @@ impl UpdateTracker, PaymentsConfirmInt let authentication_type = payment_data.payment_attempt.authentication_type; + let connector_request_reference_id = payment_data + .payment_attempt + .connector_request_reference_id + .clone(); + let payment_attempt_update = match &payment_data.payment_method { Some(payment_method) => { hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentTokenized { @@ -557,6 +582,7 @@ impl UpdateTracker, PaymentsConfirmInt connector, merchant_connector_id, authentication_type, + connector_request_reference_id, } } }; diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 2fb4696ef4..6a09e9a778 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1933,6 +1933,9 @@ async fn payment_response_update_tracker( } None => (None, None, None), }, + types::PaymentsResponseData::PaymentsCreateOrderResponse { .. } => { + (None, None, None) + } } } } diff --git a/crates/router/src/core/payments/operations/proxy_payments_intent.rs b/crates/router/src/core/payments/operations/proxy_payments_intent.rs index 0afd6efb0e..e7c97ca680 100644 --- a/crates/router/src/core/payments/operations/proxy_payments_intent.rs +++ b/crates/router/src/core/payments/operations/proxy_payments_intent.rs @@ -382,12 +382,18 @@ impl UpdateTracker, ProxyPaymentsReque .authentication_type .unwrap_or_default(); + let connector_request_reference_id = payment_data + .payment_attempt + .connector_request_reference_id + .clone(); + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { status: attempt_status, updated_by: storage_scheme.to_string(), connector, merchant_connector_id, authentication_type, + connector_request_reference_id, }; let updated_payment_intent = db diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index e8d1e756b1..0cf6e85e42 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -22,6 +22,8 @@ use error_stack::{report, ResultExt}; use hyperswitch_domain_models::ApiModelToDieselModelConvertor; use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types}; #[cfg(feature = "v2")] +use hyperswitch_interfaces::api::ConnectorSpecifications; +#[cfg(feature = "v2")] use masking::PeekInterface; use masking::{ExposeInterface, Maskable, Secret}; use router_env::{instrument, tracing}; @@ -243,10 +245,11 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .to_string(); let connector_request_reference_id = payment_data - .payment_intent - .merchant_reference_id - .map(|id| id.get_string_repr().to_owned()) - .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); + .payment_attempt + .connector_request_reference_id + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector_request_reference_id not found in payment_attempt")?; let email = customer .as_ref() @@ -312,6 +315,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( merchant_account_id: None, merchant_config_currency: None, connector_testing_data: None, + order_id: None, }; let connector_mandate_request_reference_id = payment_data .payment_attempt @@ -430,12 +434,6 @@ pub async fn construct_payment_router_data_for_capture<'a>( let payment_method = payment_data.payment_attempt.payment_method_type; - let connector_request_reference_id = payment_data - .payment_intent - .merchant_reference_id - .map(|id| id.get_string_repr().to_owned()) - .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); - let connector_mandate_request_reference_id = payment_data .payment_attempt .connector_token_details @@ -449,6 +447,13 @@ pub async fn construct_payment_router_data_for_capture<'a>( payment_data.payment_attempt.merchant_connector_id.clone(), )?; + let connector_request_reference_id = payment_data + .payment_attempt + .connector_request_reference_id + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector_request_reference_id not found in payment_attempt")?; + let amount_to_capture = payment_data .payment_attempt .amount_details @@ -589,10 +594,12 @@ pub async fn construct_router_data_for_psync<'a>( let attempt = &payment_data.payment_attempt; - let connector_request_reference_id = payment_intent - .merchant_reference_id - .map(|id| id.get_string_repr().to_owned()) - .unwrap_or(attempt.id.get_string_repr().to_owned()); + let connector_request_reference_id = payment_data + .payment_attempt + .connector_request_reference_id + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector_request_reference_id not found in payment_attempt")?; let request = types::PaymentsSyncData { amount: attempt.amount_details.get_net_amount(), @@ -934,10 +941,11 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( .to_string(); let connector_request_reference_id = payment_data - .payment_intent - .merchant_reference_id - .map(|id| id.get_string_repr().to_owned()) - .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); + .payment_attempt + .connector_request_reference_id + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector_request_reference_id not found in payment_attempt")?; let email = customer .as_ref() @@ -1867,10 +1875,20 @@ where .clone(), )?; + let next_action_containing_wait_screen = + wait_screen_next_steps_check(payment_attempt.clone())?; + let next_action = payment_attempt .redirection_data .as_ref() - .map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url }); + .map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url }) + .or(next_action_containing_wait_screen.map(|wait_screen_data| { + api_models::payments::NextActionData::WaitScreenInformation { + display_from_timestamp: wait_screen_data.display_from_timestamp, + display_to_timestamp: wait_screen_data.display_to_timestamp, + poll_config: wait_screen_data.poll_config, + } + })); let connector_token_details = payment_attempt .connector_token_details @@ -2601,6 +2619,7 @@ where api_models::payments::NextActionData::WaitScreenInformation { display_from_timestamp: wait_screen_data.display_from_timestamp, display_to_timestamp: wait_screen_data.display_to_timestamp, + poll_config: wait_screen_data.poll_config, } })) .or(payment_attempt.authentication_data.as_ref().map(|_| { @@ -3621,6 +3640,7 @@ impl TryFrom> for types::PaymentsAuthoriz merchant_account_id, merchant_config_currency, connector_testing_data, + order_id: None, }) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 53fbff1cd3..52e3f267c8 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -36,9 +36,9 @@ use hyperswitch_domain_models::router_flow_types::{ mandate_revoke::MandateRevoke, payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, - CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, - PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, - SetupMandate, UpdateMetadata, Void, + CompleteAuthorize, CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, + InitPayment, PSync, PostProcessing, PostSessionTokens, PreProcessing, Reject, + SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -68,10 +68,10 @@ pub use hyperswitch_domain_models::{ }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, BrowserInformation, ChargeRefunds, ChargeRefundsOptions, CompleteAuthorizeData, - CompleteAuthorizeRedirectResponse, ConnectorCustomerData, DefendDisputeRequestData, - DestinationChargeRefund, DirectChargeRefund, MandateRevokeRequestData, - MultipleCaptureRequestData, PaymentMethodTokenizationData, PaymentsApproveData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + CompleteAuthorizeRedirectResponse, ConnectorCustomerData, CreateOrderRequestData, + DefendDisputeRequestData, DestinationChargeRefund, DirectChargeRefund, + MandateRevokeRequestData, MultipleCaptureRequestData, PaymentMethodTokenizationData, + PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, @@ -150,6 +150,9 @@ pub type PaymentsIncrementalAuthorizationRouterData = RouterData< pub type PaymentsTaxCalculationRouterData = RouterData; +pub type CreateOrderRouterData = + RouterData; + pub type SdkSessionUpdateRouterData = RouterData; @@ -510,6 +513,11 @@ pub struct PaymentMethodTokenResult { pub connector_response: Option, } +pub struct CreateOrderResult { + pub create_order_result: Result, ErrorResponse>, + pub is_create_order_performed: bool, +} + pub struct PspTokenResult { pub token: Result, } @@ -1081,6 +1089,7 @@ impl ForeignFrom<&SetupMandateRouterData> for PaymentsAuthorizeData { merchant_account_id: None, merchant_config_currency: None, connector_testing_data: data.request.connector_testing_data.clone(), + order_id: None, } } } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 7379794aaa..7728945fe6 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -35,17 +35,17 @@ pub use api_models::{ use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentCreateIntent, - PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, PostSessionTokens, - PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, SetupMandate, UpdateMetadata, - Void, + CreateConnectorCustomer, CreateOrder, IncrementalAuthorization, InitPayment, PSync, + PaymentCreateIntent, PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, + PostSessionTokens, PreProcessing, RecordAttempt, Reject, SdkSessionUpdate, Session, + SetupMandate, UpdateMetadata, Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, PaymentSession, PaymentSessionUpdate, PaymentSync, PaymentToken, PaymentUpdateMetadata, PaymentVoid, PaymentsCompleteAuthorize, - PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, + PaymentsCreateOrder, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }; pub use super::payments_v2::{ diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index ad9a490398..c9be34037c 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -64,6 +64,7 @@ impl VerifyConnectorData { merchant_account_id: None, merchant_config_currency: None, connector_testing_data: None, + order_id: None, } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 49ead71cb2..4bc234cca2 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -579,6 +579,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::PaymentsCreateOrderResponse { .. }) => None, Err(_) => None, } } @@ -997,6 +998,7 @@ impl Default for PaymentAuthorizeType { merchant_account_id: None, merchant_config_currency: None, connector_testing_data: None, + order_id: None, }; Self(data) } @@ -1144,6 +1146,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, Ok(types::PaymentsResponseData::PaymentResourceUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::PaymentsCreateOrderResponse { .. }) => None, Err(_) => None, } } diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 20bbb187c9..b3c6683f0b 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -163,7 +163,7 @@ plaid.base_url = "https://sandbox.plaid.com" powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" -razorpay.base_url = "https://sandbox.juspay.in/" +razorpay.base_url = "https://api.razorpay.com/" recurly.base_url = "https://v3.recurly.com" redsys.base_url = "https://sis-t.redsys.es:25443" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/down.sql b/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/down.sql new file mode 100644 index 0000000000..da0137560b --- /dev/null +++ b/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS connector_request_reference_id; \ No newline at end of file diff --git a/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/up.sql b/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/up.sql new file mode 100644 index 0000000000..cda71f826f --- /dev/null +++ b/v2_migrations/2025-05-30-103514_add_connector_request_reference_id_in_payment_attempt/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS connector_request_reference_id VARCHAR(255); \ No newline at end of file