From ff14b7cac84f23133f314a1ac337d04e5e845fce Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:08:45 +0530 Subject: [PATCH] feat(core): Add Merchant Tax ID in Merchant Profile (#8992) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/v1/openapi_spec_v1.json | 5 +++ api-reference/v2/openapi_spec_v2.json | 5 +++ crates/api_models/src/admin.rs | 3 ++ .../connectors/worldpayvantiv/transformers.rs | 31 +++++++++++++++---- .../src/merchant_account.rs | 16 ++++++++++ .../src/router_data.rs | 1 + .../router/src/core/payments/transformers.rs | 4 +++ 7 files changed, 59 insertions(+), 6 deletions(-) diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index cc7e27495d..de0740d6c0 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -18941,6 +18941,11 @@ } ], "nullable": true + }, + "merchant_tax_registration_id": { + "type": "string", + "example": "123456789", + "nullable": true } }, "additionalProperties": false diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 02fd922221..a3fb1213f3 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -14655,6 +14655,11 @@ } ], "nullable": true + }, + "merchant_tax_registration_id": { + "type": "string", + "example": "123456789", + "nullable": true } }, "additionalProperties": false diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 5425e7c7b4..81ec2ce218 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -662,6 +662,9 @@ pub struct MerchantDetails { /// The merchant's address details pub address: Option, + + #[schema(value_type = Option, example = "123456789")] + pub merchant_tax_registration_id: Option>, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)] #[serde(deny_unknown_fields)] diff --git a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs index ed17500469..98761cb8cc 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs @@ -316,7 +316,7 @@ pub struct EnhancedData { #[serde(skip_serializing_if = "Option::is_none")] pub destination_country_code: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub invoice_reference_number: Option, + pub detail_tax: Option, #[serde(skip_serializing_if = "Option::is_none")] pub line_item_data: Option>, } @@ -324,10 +324,11 @@ pub struct EnhancedData { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DetailTax { + #[serde(skip_serializing_if = "Option::is_none")] pub tax_included_in_total: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tax_amount: Option, - pub tax_rate: Option, - pub tax_type_identifier: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub card_acceptor_tax_id: Option>, } @@ -356,7 +357,6 @@ pub struct LineItemData { pub commodity_code: Option, #[serde(skip_serializing_if = "Option::is_none")] pub unit_cost: Option, - // pub detail_tax: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -938,7 +938,7 @@ where { let l2_l3_data = item.get_optional_l2_l3_data(); if let Some(l2_l3_data) = l2_l3_data { - let line_item_data = l2_l3_data.order_details.map(|order_details| { + let line_item_data = l2_l3_data.order_details.as_ref().map(|order_details| { order_details .iter() .enumerate() @@ -969,6 +969,25 @@ where let customer_reference = get_vantiv_customer_reference(&l2_l3_data.merchant_order_reference_id); + let detail_tax: Option = if l2_l3_data.merchant_tax_registration_id.is_some() + && l2_l3_data.order_details.is_some() + { + Some(DetailTax { + tax_included_in_total: match tax_exempt { + Some(false) => Some(true), + Some(true) | None => Some(false), + }, + card_acceptor_tax_id: l2_l3_data.merchant_tax_registration_id.clone(), + tax_amount: l2_l3_data.order_details.as_ref().map(|orders| { + orders + .iter() + .filter_map(|order| order.total_tax_amount) + .fold(MinorUnit::zero(), |acc, tax| acc + tax) + }), + }) + } else { + None + }; let enhanced_data = EnhancedData { customer_reference, sales_tax: l2_l3_data.order_tax_amount, @@ -979,7 +998,7 @@ where ship_from_postal_code: l2_l3_data.shipping_origin_zip, destination_postal_code: l2_l3_data.shipping_destination_zip, destination_country_code: l2_l3_data.shipping_country, - invoice_reference_number: l2_l3_data.merchant_order_reference_id, + detail_tax, line_item_data, }; Ok(Some(enhanced_data)) diff --git a/crates/hyperswitch_domain_models/src/merchant_account.rs b/crates/hyperswitch_domain_models/src/merchant_account.rs index d272ff5269..29105e44e2 100644 --- a/crates/hyperswitch_domain_models/src/merchant_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_account.rs @@ -223,6 +223,22 @@ impl MerchantAccount { &self.organization_id } + /// Get the merchant_details from MerchantAccount + pub fn get_merchant_details(&self) -> &OptionalEncryptableValue { + &self.merchant_details + } + + /// Extract merchant_tax_registration_id from merchant_details + pub fn get_merchant_tax_registration_id(&self) -> Option> { + self.merchant_details.as_ref().and_then(|details| { + details + .get_inner() + .peek() + .get("merchant_tax_registration_id") + .and_then(|id| id.as_str().map(|s| Secret::new(s.to_string()))) + }) + } + /// Check whether the merchant account is a platform account pub fn is_platform_account(&self) -> bool { matches!( diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 3177b1b0a4..56824d9610 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -132,6 +132,7 @@ pub struct L2L3Data { pub shipping_country: Option, pub shipping_destination_zip: Option>, pub billing_address_city: Option, + pub merchant_tax_registration_id: Option>, } // Different patterns of authentication. diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 9f7dd008d4..cfdf17b627 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1548,6 +1548,9 @@ where let l2_l3_data = state.conf.l2_l3_data_config.enabled.then(|| { let shipping_address = unified_address.get_shipping(); let billing_address = unified_address.get_payment_billing(); + let merchant_tax_registration_id = merchant_context + .get_merchant_account() + .get_merchant_tax_registration_id(); types::L2L3Data { order_date: payment_data.payment_intent.order_date, @@ -1590,6 +1593,7 @@ where .as_ref() .and_then(|addr| addr.address.as_ref()) .and_then(|details| details.city.clone()), + merchant_tax_registration_id, } }); crate::logger::debug!("unified address details {:?}", unified_address);