diff --git a/Cargo.lock b/Cargo.lock index 5b4d9767029..b6b54a66a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4005,6 +4005,7 @@ dependencies = [ "bytes 1.7.1", "cards", "common_enums", + "common_types", "common_utils", "encoding_rs", "error-stack", diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 228aef09dee..2ea34e84690 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -7392,6 +7392,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -20924,6 +20935,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -20951,6 +20973,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -22436,6 +22469,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 587cba45e6a..78760af592a 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9623,6 +9623,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -25482,6 +25493,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -25509,6 +25531,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -27054,6 +27087,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs index 65f6ae9ad68..0361f5b0e46 100644 --- a/crates/common_types/src/domain.rs +++ b/crates/common_types/src/domain.rs @@ -41,3 +41,15 @@ pub struct AdyenSplitItem { pub description: Option, } impl_to_sql_from_sql_json!(AdyenSplitItem); + +/// Fee information to be charged on the payment being collected for sub-merchant via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitSubMerchantData { + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitSubMerchantData); diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index ba96d618c41..996478e8bfa 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -12,7 +12,7 @@ use euclid::frontend::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -26,6 +26,8 @@ pub enum SplitPaymentsRequest { StripeSplitPayment(StripeSplitPaymentRequest), /// AdyenSplitPayment AdyenSplitPayment(AdyenSplitData), + /// XenditSplitPayment + XenditSplitPayment(XenditSplitRequest), } impl_to_sql_from_sql_json!(SplitPaymentsRequest); @@ -156,6 +158,99 @@ pub enum ConnectorChargeResponseData { StripeSplitPayment(StripeChargeResponseData), /// AdyenChargeResponseData AdyenSplitPayment(AdyenSplitData), + /// XenditChargeResponseData + XenditSplitPayment(XenditChargeResponseData), } impl_to_sql_from_sql_json!(ConnectorChargeResponseData); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitRoute { + /// Amount of payments to be split + pub flat_amount: Option, + /// Amount of payments to be split, using a percent rate as unit + pub percent_amount: Option, + /// Currency code + #[schema(value_type = Currency, example = "USD")] + pub currency: enums::Currency, + /// ID of the destination account where the amount will be routed to + pub destination_account_id: String, + /// Reference ID which acts as an identifier of the route itself + pub reference_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitRoute); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitRequest { + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitRequest); + +/// Xendit Charge Request +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditSplitRequest { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitRequest), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditSplitRequest); + +/// Charge Information +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditChargeResponseData { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitResponse), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditChargeResponseData); + +/// Fee information charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitResponse { + /// Identifier for split rule created for the payment + pub split_rule_id: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitResponse); diff --git a/crates/common_types/src/refunds.rs b/crates/common_types/src/refunds.rs index c96937021c8..f7c12f90f84 100644 --- a/crates/common_types/src/refunds.rs +++ b/crates/common_types/src/refunds.rs @@ -5,7 +5,7 @@ use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -19,6 +19,8 @@ pub enum SplitRefund { StripeSplitRefund(StripeSplitRefundRequest), /// AdyenSplitRefundRequest AdyenSplitRefund(AdyenSplitData), + /// XenditSplitRefundRequest + XenditSplitRefund(XenditSplitSubMerchantData), } impl_to_sql_from_sql_json!(SplitRefund); diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 13d5266dabd..915b87a9189 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -49,6 +49,7 @@ lazy_static = "1.4.0" api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false } cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } +common_types = { version = "0.1.0", path = "../common_types" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics"] } hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces", default-features = false } diff --git a/crates/hyperswitch_connectors/src/connectors/xendit.rs b/crates/hyperswitch_connectors/src/connectors/xendit.rs index dae6b8fd88f..c45f637f48a 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit.rs @@ -14,18 +14,22 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, SplitRefundsRequest, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -61,6 +65,7 @@ impl Xendit { } } impl api::Payment for Xendit {} +impl api::PaymentsPreProcessing for Xendit {} impl api::PaymentSession for Xendit {} impl api::ConnectorAccessToken for Xendit {} impl api::MandateSetup for Xendit {} @@ -121,6 +126,7 @@ impl ConnectorCommon for Xendit { let auth = xendit::XenditAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -203,7 +209,46 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + if let Ok(PaymentsResponseData::TransactionResponse { + charges: + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits( + xendit_response, + ), + )), + .. + }) = req.response.as_ref() + { + headers.push(( + xendit::auth_headers::WITH_SPLIT_RULE.to_string(), + xendit_response.split_rule_id.clone().into(), + )); + if let Some(for_user_id) = &xendit_response.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -280,13 +325,121 @@ impl ConnectorIntegration + for Xendit +{ + fn get_headers( + &self, + req: &PaymentsPreProcessingRouterData, + 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: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/split_rules", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = xendit::XenditSplitRequestData::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &PaymentsPreProcessingRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditSplitResponse = res + .response + .parse_struct("XenditSplitResponse") + .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 Xendit { fn get_headers( &self, req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -353,7 +506,31 @@ impl ConnectorIntegration fo req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -453,7 +630,17 @@ impl ConnectorIntegration for Xendit req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -539,7 +726,17 @@ impl ConnectorIntegration for Xendit { req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs index 7a7789e73ab..79ac7312350 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -7,13 +7,15 @@ use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::{PaymentsAuthorizeData, PaymentsCaptureData, ResponseId}, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCaptureData, PaymentsPreProcessingData, ResponseId, + }, router_response_types::{ MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -89,6 +91,38 @@ pub struct XenditPaymentsRequest { pub channel_properties: Option, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRoute { + #[serde(skip_serializing_if = "Option::is_none")] + pub flat_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub percent_amount: Option, + pub currency: enums::Currency, + pub destination_account_id: String, + pub reference_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRequest { + pub name: String, + pub description: String, + pub routes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitRequestData { + #[serde(flatten)] + pub split_data: XenditSplitRequest, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitResponse { + id: String, + name: String, + description: String, + routes: Vec, +} + #[derive(Serialize, Deserialize, Debug)] pub struct CardInfo { pub channel_properties: ChannelProperties, @@ -104,6 +138,11 @@ pub struct CardInformation { pub cardholder_email: pii::Email, pub cardholder_phone_number: Secret, } +pub mod auth_headers { + pub const WITH_SPLIT_RULE: &str = "with-split-rule"; + pub const FOR_USER_ID: &str = "for-user-id"; +} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionType { @@ -288,6 +327,35 @@ impl status_code: item.http_code, }) } else { + let charges = match item.data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => item + .data + .response + .as_ref() + .ok() + .and_then(|response| match response { + PaymentsResponseData::TransactionResponse { charges, .. } => { + charges.clone() + } + _ => None, + }), + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(ref split_data), + )) => { + let charges = common_types::domain::XenditSplitSubMerchantData { + for_user_id: split_data.for_user_id.clone(), + }; + Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::SingleSplit(charges), + ), + ) + } + _ => None, + }; + Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: match item.response.actions { @@ -320,7 +388,7 @@ impl item.response.reference_id.peek().to_string(), ), incremental_authorization_allowed: None, - charges: None, + charges, }) }; Ok(Self { @@ -388,6 +456,90 @@ impl }) } } + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: ResponseRouterData< + F, + XenditSplitResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + ) -> Result { + let for_user_id = match item.data.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) => split_data.for_user_id.clone(), + _ => None, + }; + + let routes: Vec = item + .response + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert_back( + &required_conversion_type, + amount, + item.data.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| common_types::payments::XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let charges = common_types::payments::XenditMultipleSplitResponse { + split_rule_id: item.response.id, + for_user_id, + name: item.response.name, + description: item.response.description, + routes, + }; + + let response = 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: Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits(charges), + ), + ), + }; + + Ok(Self { + response: Ok(response), + ..item.data + }) + } +} + impl TryFrom> for PaymentsSyncRouterData { type Error = error_stack::Report; fn try_from( @@ -461,6 +613,59 @@ impl TryFrom<&ConnectorAuthType> for XenditAuthType { } } +impl TryFrom<&PaymentsPreProcessingRouterData> for XenditSplitRequestData { + type Error = error_stack::Report; + fn try_from(item: &PaymentsPreProcessingRouterData) -> Result { + if let Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) = item.request.split_payments.clone() + { + let routes: Vec = split_data + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert( + &required_conversion_type, + amount, + item.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let split_data = XenditSplitRequest { + name: split_data.name.clone(), + description: split_data.description.clone(), + routes, + }; + + Ok(Self { split_data }) + } else { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Xendit"), + ) + .into()) + } + } +} + //TODO: Fill the struct with respective fields // REFUND : // Type definition for RefundRequest @@ -538,3 +743,8 @@ impl TryFrom> for RefundsRouter }) } } + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct XenditMetadata { + pub for_user_id: String, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index cc8a785e2df..d1a2596228a 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -825,7 +825,6 @@ default_imp_for_pre_processing_steps!( connectors::Worldpay, connectors::Wellsfargo, connectors::Volt, - connectors::Xendit, connectors::Zen, connectors::Zsl, connectors::CtpMastercard diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 0b98885fdaf..606a8af51c3 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -369,6 +369,8 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, /// Identifier for the platform merchant. pub platform_merchant_id: Option, + /// Split Payment Data + pub split_payments: Option, } #[cfg(feature = "v2")] @@ -511,6 +513,7 @@ impl PaymentIntent { routing_algorithm_id: request.routing_algorithm_id, platform_merchant_id: platform_merchant_id .map(|merchant_account| merchant_account.get_id().to_owned()), + split_payments: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 9d4debacaba..cc1a7054ded 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1475,6 +1475,7 @@ impl behaviour::Conversion for PaymentIntent { routing_algorithm_id, payment_link_config, platform_merchant_id, + split_payments, } = self; Ok(DieselPaymentIntent { skip_external_tax_calculation: Some(amount_details.get_external_tax_action_as_bool()), @@ -1547,7 +1548,7 @@ impl behaviour::Conversion for PaymentIntent { psd2_sca_exemption_type: None, request_extended_authorization: None, platform_merchant_id, - split_payments: None, + split_payments, }) } async fn convert_back( @@ -1674,6 +1675,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config: storage_model.payment_link_config, routing_algorithm_id: storage_model.routing_algorithm_id, platform_merchant_id: storage_model.platform_merchant_id, + split_payments: storage_model.split_payments, }) } .await diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 90f97816091..071a77aa7bd 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -118,7 +118,7 @@ pub struct PaymentsCaptureData { pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. pub capture_method: Option, - + pub split_payments: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, pub minor_amount_to_capture: MinorUnit, @@ -291,6 +291,7 @@ pub struct PaymentsPreProcessingData { pub related_transaction_id: Option, pub redirect_response: Option, pub metadata: Option>, + pub split_payments: Option, // New amount for amount frame work pub minor_amount: Option, @@ -320,6 +321,7 @@ impl TryFrom for PaymentsPreProcessingData { related_transaction_id: data.related_transaction_id, redirect_response: None, enrolled_for_3ds: data.enrolled_for_3ds, + split_payments: data.split_payments, metadata: data.metadata.map(Secret::new), }) } @@ -348,6 +350,7 @@ impl TryFrom for PaymentsPreProcessingData { mandate_id: data.mandate_id, related_transaction_id: None, redirect_response: data.redirect_response, + split_payments: None, enrolled_for_3ds: true, metadata: data.connector_meta.map(Secret::new), }) @@ -648,6 +651,7 @@ pub struct RefundIntegrityObject { pub enum SplitRefundsRequest { StripeSplitRefund(StripeSplitRefund), AdyenSplitRefund(common_types::domain::AdyenSplitData), + XenditSplitRefund(common_types::domain::XenditSplitSubMerchantData), } #[derive(Debug, serde::Deserialize, Clone)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index eb5edb08fab..b75d1d22854 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -220,6 +220,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, common_types::domain::AdyenSplitItem, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_utils::types::ChargeRefunds, common_types::refunds::SplitRefund, common_types::refunds::StripeSplitRefundRequest, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 12e42320b40..752bd9707ff 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -165,6 +165,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::SplitPaymentsRequest, common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_types::domain::AdyenSplitItem, common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 66e20e54616..a971bd96741 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -1520,15 +1520,13 @@ impl services::ConnectorIntegration { - RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( - req, - refund_amount, - ))?)) - } Some(SplitRefundsRequest::StripeSplitRefund(_)) => RequestContent::FormUrlEncoded( Box::new(stripe::ChargeRefundRequest::try_from(req)?), ), + _ => RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( + req, + refund_amount, + ))?)), }; Ok(request_body) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 7611f375c09..cdcf7b4c7b2 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1973,9 +1973,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }; (charges, None) } - Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) | None => { - (None, item.connector_customer.to_owned().map(Secret::new)) - } + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) + | Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) + | None => (None, item.connector_customer.to_owned().map(Secret::new)), }; Ok(Self { @@ -3092,11 +3092,9 @@ impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { }, }) } - types::SplitRefundsRequest::AdyenSplitRefund(_) => { - Err(errors::ConnectorError::MissingRequiredField { - field_name: "stripe_split_refund", - })? - } + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "stripe_split_refund", + })?, }, } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index d97a52ab55b..74ac9dc0f37 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4202,6 +4202,17 @@ where { router_data = router_data.preprocessing_steps(state, connector).await?; (router_data, should_continue_payment) + } else if connector.connector_name == router_types::Connector::Xendit { + match payment_data.get_payment_intent().split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + router_data = router_data.preprocessing_steps(state, connector).await?; + let is_error_in_response = router_data.response.is_err(); + (router_data, !is_error_in_response) + } + _ => (router_data, should_continue_payment), + } } else { (router_data, should_continue_payment) } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 7efa9a5e180..b5104391ab8 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -626,6 +626,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: item.request.split_payments, }) } } diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 44bb9f2703a..6e8152716d0 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -318,6 +318,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: None, }) } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4bfc0db5537..1803e12f0b2 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6993,6 +6993,70 @@ pub fn validate_platform_request_for_marketplace( Ok(()) })?; } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + xendit_split_payment, + )) => match xendit_split_payment { + common_types::payments::XenditSplitRequest::MultipleSplits( + xendit_multiple_split_payment, + ) => { + match amount { + api::Amount::Zero => { + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .iter() + .map(|route| { + route + .flat_amount + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64() + }) + .sum(); + + if total_split_amount != 0 { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: + "Sum of split amounts should be equal to the total amount", + }); + } + } + api::Amount::Value(amount) => { + let total_payment_amount: i64 = amount.into(); + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .into_iter() + .map(|route| { + if route.flat_amount.is_none() && route.percent_amount.is_none() { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount to be provided".to_string(), + }) + } else if route.flat_amount.is_some() && route.percent_amount.is_some(){ + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount, but not both".to_string(), + }) + } else { + Ok(route + .flat_amount + .map(|amount| amount.get_amount_as_i64()) + .or(route.percent_amount.map(|percentage| (percentage * total_payment_amount) / 100)) + .unwrap_or(0)) + } + }) + .collect::, _>>()? + .into_iter() + .sum(); + + if total_payment_amount < total_split_amount { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: + "The sum of split amounts should not exceed the total amount" + .to_string(), + }); + } + } + }; + } + common_types::payments::XenditSplitRequest::SingleSplit(_) => (), + }, None => (), } Ok(()) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 630f6176726..5c77452b677 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -474,6 +474,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }; // TODO: evaluate the fields in router data, if they are required or not @@ -3341,6 +3342,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }) } } @@ -3396,6 +3398,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info, metadata: payment_data.payment_intent.metadata, integrity_object: None, + split_payments: payment_data.payment_intent.split_payments, }) } } @@ -4114,6 +4117,7 @@ impl TryFrom> for types::PaymentsPreProce mandate_id: payment_data.mandate_id, related_transaction_id: None, enrolled_for_3ds: true, + split_payments: payment_data.payment_intent.split_payments, metadata: payment_data.payment_intent.metadata.map(Secret::new), }) } diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 90c4a7c4434..573543dfbf7 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -252,3 +252,32 @@ pub fn validate_adyen_charge_refund( } Ok(()) } +pub fn validate_xendit_charge_refund( + xendit_split_payment_response: &common_types::payments::XenditChargeResponseData, + xendit_split_refund_request: &common_types::domain::XenditSplitSubMerchantData, +) -> RouterResult> { + match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id + != Some(xendit_split_refund_request.for_user_id.clone()) + { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + common_types::payments::XenditChargeResponseData::SingleSplit( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id != xendit_split_refund_request.for_user_id { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + } +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 49075e7f548..8f5a80b24db 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -538,6 +538,58 @@ pub fn get_split_refunds( _ => Ok(None), } } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) => { + match ( + &split_refund_input.payment_charges, + &split_refund_input.refund_request, + ) { + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + Some(common_types::refunds::SplitRefund::XenditSplitRefund( + split_refund_request, + )), + ) => { + let user_id = super::refunds::validator::validate_xendit_charge_refund( + xendit_split_payment_response, + split_refund_request, + )?; + + Ok(user_id.map(|for_user_id| { + router_request_types::SplitRefundsRequest::XenditSplitRefund( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) + })) + } + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + None, + ) => { + let option_for_user_id = match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + common_types::payments::XenditMultipleSplitResponse { + for_user_id, .. + }, + ) => for_user_id.clone(), + common_types::payments::XenditChargeResponseData::SingleSplit( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) => Some(for_user_id.clone()), + }; + + if option_for_user_id.is_some() { + Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "split_refunds.xendit_split_refund.for_user_id", + })? + } else { + Ok(None) + } + } + _ => Ok(None), + } + } _ => Ok(None), } }