diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 8f7a072881a..f5a1a9b1ab7 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -7246,6 +7246,13 @@ "example": "Doe", "nullable": true, "maxLength": 255 + }, + "origin_zip": { + "type": "string", + "description": "The zip/postal code of the origin", + "example": "08807", + "nullable": true, + "maxLength": 50 } }, "additionalProperties": false @@ -13362,6 +13369,12 @@ "example": "+1", "nullable": true, "maxLength": 2 + }, + "tax_registration_id": { + "type": "string", + "description": "The tax registration identifier of the customer.", + "nullable": true, + "maxLength": 255 } } }, @@ -13705,6 +13718,13 @@ "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", "nullable": true + }, + "tax_registration_id": { + "type": "string", + "description": "Customer's tax registration ID", + "example": "123456789", + "nullable": true, + "maxLength": 255 } } }, @@ -13782,6 +13802,13 @@ "example": "pm_djh2837dwduh890123", "nullable": true, "maxLength": 64 + }, + "tax_registration_id": { + "type": "string", + "description": "The customer's tax registration number.", + "example": "123456789", + "nullable": true, + "maxLength": 255 } } }, @@ -13836,6 +13863,13 @@ "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", "nullable": true + }, + "tax_registration_id": { + "type": "string", + "description": "Customer's tax registration ID", + "example": "123456789", + "nullable": true, + "maxLength": 255 } } }, @@ -19410,6 +19444,43 @@ "type": "string", "description": "The tax code for the product", "nullable": true + }, + "description": { + "type": "string", + "description": "Description for the item", + "nullable": true + }, + "sku": { + "type": "string", + "description": "Stock Keeping Unit (SKU) or the item identifier for this item.", + "nullable": true + }, + "upc": { + "type": "string", + "description": "Universal Product Code for the item.", + "nullable": true + }, + "commodity_code": { + "type": "string", + "description": "Code describing a commodity or a group of commodities pertaining to goods classification.", + "nullable": true + }, + "unit_of_measure": { + "type": "string", + "description": "Unit of measure used for the item quantity.", + "nullable": true + }, + "total_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount for the item.", + "nullable": true + }, + "unit_discount_amount": { + "type": "integer", + "format": "int64", + "description": "Discount amount applied to this item.", + "nullable": true } } }, @@ -22502,6 +22573,43 @@ } ], "nullable": true + }, + "tax_status": { + "allOf": [ + { + "$ref": "#/components/schemas/TaxStatus" + } + ], + "nullable": true + }, + "discount_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount of the discount you have applied to the order or transaction.", + "example": 6540, + "nullable": true + }, + "shipping_amount_tax": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "duty_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_date": { + "type": "string", + "format": "date-time", + "description": "Date the payer placed the order.", + "nullable": true } } }, @@ -22933,6 +23041,43 @@ } ], "nullable": true + }, + "tax_status": { + "allOf": [ + { + "$ref": "#/components/schemas/TaxStatus" + } + ], + "nullable": true + }, + "discount_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount of the discount you have applied to the order or transaction.", + "example": 6540, + "nullable": true + }, + "shipping_amount_tax": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "duty_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_date": { + "type": "string", + "format": "date-time", + "description": "Date the payer placed the order.", + "nullable": true } } }, @@ -24250,6 +24395,43 @@ } ], "nullable": true + }, + "tax_status": { + "allOf": [ + { + "$ref": "#/components/schemas/TaxStatus" + } + ], + "nullable": true + }, + "discount_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount of the discount you have applied to the order or transaction.", + "example": 6540, + "nullable": true + }, + "shipping_amount_tax": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "duty_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_date": { + "type": "string", + "format": "date-time", + "description": "Date the payer placed the order.", + "nullable": true } }, "additionalProperties": false @@ -25436,6 +25618,43 @@ } ], "nullable": true + }, + "tax_status": { + "allOf": [ + { + "$ref": "#/components/schemas/TaxStatus" + } + ], + "nullable": true + }, + "discount_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount of the discount you have applied to the order or transaction.", + "example": 6540, + "nullable": true + }, + "shipping_amount_tax": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "duty_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_date": { + "type": "string", + "format": "date-time", + "description": "Date the payer placed the order.", + "nullable": true } } }, @@ -31001,6 +31220,13 @@ "SwishQrData": { "type": "object" }, + "TaxStatus": { + "type": "string", + "enum": [ + "taxable", + "exempt" + ] + }, "ThirdPartySdkSessionResponse": { "type": "object", "required": [ diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 73be93881d6..8039b1be72f 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -4070,6 +4070,13 @@ "example": "Doe", "nullable": true, "maxLength": 255 + }, + "origin_zip": { + "type": "string", + "description": "The zip/postal code of the origin", + "example": "08807", + "nullable": true, + "maxLength": 50 } }, "additionalProperties": false @@ -9664,6 +9671,12 @@ "example": "+1", "nullable": true, "maxLength": 2 + }, + "tax_registration_id": { + "type": "string", + "description": "The tax registration identifier of the customer.", + "nullable": true, + "maxLength": 255 } } }, @@ -9876,6 +9889,13 @@ "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", "nullable": true + }, + "tax_registration_id": { + "type": "string", + "description": "The customer's tax registration number.", + "example": "123456789", + "nullable": true, + "maxLength": 255 } }, "additionalProperties": false @@ -9975,6 +9995,13 @@ "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8", "nullable": true, "maxLength": 64 + }, + "tax_registration_id": { + "type": "string", + "description": "The customer's tax registration number.", + "example": "123456789", + "nullable": true, + "maxLength": 255 } } }, @@ -10044,6 +10071,13 @@ "description": "The unique identifier of the payment method", "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8", "nullable": true + }, + "tax_registration_id": { + "type": "string", + "description": "The customer's tax registration number.", + "example": "123456789", + "nullable": true, + "maxLength": 255 } }, "additionalProperties": false @@ -15223,6 +15257,43 @@ "type": "string", "description": "The tax code for the product", "nullable": true + }, + "description": { + "type": "string", + "description": "Description for the item", + "nullable": true + }, + "sku": { + "type": "string", + "description": "Stock Keeping Unit (SKU) or the item identifier for this item.", + "nullable": true + }, + "upc": { + "type": "string", + "description": "Universal Product Code for the item.", + "nullable": true + }, + "commodity_code": { + "type": "string", + "description": "Code describing a commodity or a group of commodities pertaining to goods classification.", + "nullable": true + }, + "unit_of_measure": { + "type": "string", + "description": "Unit of measure used for the item quantity.", + "nullable": true + }, + "total_amount": { + "type": "integer", + "format": "int64", + "description": "Total amount for the item.", + "nullable": true + }, + "unit_discount_amount": { + "type": "integer", + "format": "int64", + "description": "Discount amount applied to this item.", + "nullable": true } } }, @@ -24734,6 +24805,13 @@ "calculate" ] }, + "TaxStatus": { + "type": "string", + "enum": [ + "taxable", + "exempt" + ] + }, "ThirdPartySdkSessionResponse": { "type": "object", "required": [ diff --git a/config/deployments/production.toml b/config/deployments/production.toml index dbc4cc781ea..6489e0ef963 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -794,6 +794,9 @@ globalpay = { long_lived_token = false, payment_method = "card", flow = "mandate outgoing_enabled = true redis_lock_expiry_seconds = 180 +[l2_l3_data_config] +enabled = "true" + [webhook_source_verification_call] connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 507115ba490..beb4a80eb38 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -800,6 +800,9 @@ globalpay = { long_lived_token = false, payment_method = "card", flow = "mandate outgoing_enabled = true redis_lock_expiry_seconds = 180 +[l2_l3_data_config] +enabled = "true" + [webhook_source_verification_call] connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call diff --git a/config/development.toml b/config/development.toml index a7f6a518b3d..77f0e8b3b64 100644 --- a/config/development.toml +++ b/config/development.toml @@ -1244,6 +1244,9 @@ dynamic_routing_enabled = false static_routing_enabled = false url = "http://localhost:8080" +[l2_l3_data_config] +enabled = "true" + [grpc_client.unified_connector_service] host = "localhost" port = 8000 diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs index 56808113fc8..19016e292a8 100644 --- a/crates/api_models/src/customers.rs +++ b/crates/api_models/src/customers.rs @@ -39,6 +39,9 @@ pub struct CustomerRequest { /// object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, + /// Customer's tax registration ID + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: Option>, } #[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)] @@ -102,6 +105,9 @@ pub struct CustomerRequest { /// object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, + /// The customer's tax registration number. + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: Option>, } #[cfg(feature = "v2")] @@ -159,6 +165,9 @@ pub struct CustomerResponse { /// The identifier for the default payment method. #[schema(max_length = 64, example = "pm_djh2837dwduh890123")] pub default_payment_method_id: Option, + /// The customer's tax registration number. + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: crypto::OptionalEncryptableSecretString, } #[cfg(feature = "v1")] @@ -218,6 +227,9 @@ pub struct CustomerResponse { /// The identifier for the default payment method. #[schema(value_type = Option, max_length = 64, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] pub default_payment_method_id: Option, + /// The customer's tax registration number. + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: crypto::OptionalEncryptableSecretString, } #[cfg(feature = "v2")] @@ -300,6 +312,9 @@ pub struct CustomerUpdateRequest { /// object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, + /// Customer's tax registration ID + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: Option>, } #[cfg(feature = "v1")] @@ -342,6 +357,9 @@ pub struct CustomerUpdateRequest { /// The unique identifier of the payment method #[schema(value_type = Option, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] pub default_payment_method_id: Option, + /// The customer's tax registration number. + #[schema(max_length = 255, value_type = Option, example = "123456789")] + pub tax_registration_id: Option>, } #[cfg(feature = "v2")] diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index b76423ecd8a..f16bdd76274 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -2622,6 +2622,7 @@ impl PaymentMethodRecord { zip: self.billing_address_zip.clone(), first_name: self.billing_address_first_name.clone(), last_name: self.billing_address_last_name.clone(), + origin_zip: None, }) } else { None diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index aedd983d91a..794aebe84d8 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -105,6 +105,10 @@ pub struct CustomerDetails { /// The country code for the customer's phone number #[schema(max_length = 2, example = "+1")] pub phone_country_code: Option, + + /// The tax registration identifier of the customer. + #[schema(value_type=Option,max_length = 255)] + pub tax_registration_id: Option>, } #[cfg(feature = "v1")] @@ -1192,6 +1196,24 @@ pub struct PaymentsRequest { /// Indicates how the payment was initiated (e.g., ecommerce, mail, or telephone). #[schema(value_type = Option)] pub payment_channel: Option, + + /// Your tax status for this order or transaction. + #[schema(value_type = Option)] + pub tax_status: Option, + + /// Total amount of the discount you have applied to the order or transaction. + #[schema(value_type = Option, example = 6540)] + pub discount_amount: Option, + + /// Tax amount applied to shipping charges. + pub shipping_amount_tax: Option, + + /// Duty or customs fee amount for international transactions. + pub duty_amount: Option, + + /// Date the payer placed the order. + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub order_date: Option, } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -1263,6 +1285,7 @@ impl PaymentsRequest { email, phone, phone_country_code, + .. }) = self.customer.as_ref() { let invalid_fields = [ @@ -1377,6 +1400,7 @@ mod payments_request_test { email: None, phone: None, phone_country_code: None, + tax_registration_id: None, }; let payments_request = PaymentsRequest { @@ -1401,6 +1425,7 @@ mod payments_request_test { email: None, phone: None, phone_country_code: None, + tax_registration_id: None, }; let payments_request = PaymentsRequest { @@ -4417,6 +4442,10 @@ pub struct AddressDetails { /// The last name for the address #[schema(value_type = Option, max_length = 255, example = "Doe")] pub last_name: Option>, + + /// The zip/postal code of the origin + #[schema(value_type = Option, max_length = 50, example = "08807")] + pub origin_zip: Option>, } impl AddressDetails { @@ -4454,6 +4483,7 @@ impl AddressDetails { line3: self.line3.or(other.line3.clone()), zip: self.zip.or(other.zip.clone()), state: self.state.or(other.state.clone()), + origin_zip: self.origin_zip.or(other.origin_zip.clone()), } } else { self @@ -6612,6 +6642,22 @@ pub struct OrderDetailsWithAmount { pub product_type: Option, /// The tax code for the product pub product_tax_code: Option, + /// Description for the item + pub description: Option, + /// Stock Keeping Unit (SKU) or the item identifier for this item. + pub sku: Option, + /// Universal Product Code for the item. + pub upc: Option, + /// Code describing a commodity or a group of commodities pertaining to goods classification. + pub commodity_code: Option, + /// Unit of measure used for the item quantity. + pub unit_of_measure: Option, + /// Total amount for the item. + #[schema(value_type = Option)] + pub total_amount: Option, // total_amount, + /// Discount amount applied to this item. + #[schema(value_type = Option)] + pub unit_discount_amount: Option, } impl masking::SerializableSecret for OrderDetailsWithAmount {} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 99b8c2e600e..1f561f6f018 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -7265,6 +7265,26 @@ pub enum MerchantDecision { Rejected, AutoRefunded, } +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + strum::EnumIter, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum TaxStatus { + Taxable, + Exempt, +} #[derive( Clone, diff --git a/crates/diesel_models/src/address.rs b/crates/diesel_models/src/address.rs index 06b82cb2c20..2d8fe990ae6 100644 --- a/crates/diesel_models/src/address.rs +++ b/crates/diesel_models/src/address.rs @@ -27,6 +27,7 @@ pub struct AddressNew { pub modified_at: PrimitiveDateTime, pub updated_by: String, pub email: Option, + pub origin_zip: Option, } #[derive(Clone, Debug, Queryable, Identifiable, Selectable, Serialize, Deserialize)] @@ -51,6 +52,7 @@ pub struct Address { pub payment_id: Option, pub updated_by: String, pub email: Option, + pub origin_zip: Option, } #[derive(Clone)] @@ -65,6 +67,7 @@ pub struct EncryptableAddress { pub last_name: crypto::OptionalEncryptableSecretString, pub phone_number: crypto::OptionalEncryptableSecretString, pub email: crypto::OptionalEncryptableEmail, + pub origin_zip: crypto::OptionalEncryptableSecretString, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)] @@ -84,6 +87,7 @@ pub struct AddressUpdateInternal { pub modified_at: PrimitiveDateTime, pub updated_by: String, pub email: Option, + pub origin_zip: Option, } impl AddressUpdateInternal { @@ -102,6 +106,7 @@ impl AddressUpdateInternal { country_code: self.country_code, modified_at: self.modified_at, updated_by: self.updated_by, + origin_zip: self.origin_zip, ..source } } diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index ae6ff49d106..325735a92a1 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -28,6 +28,7 @@ pub struct CustomerNew { pub address_id: Option, pub updated_by: Option, pub version: ApiVersion, + pub tax_registration_id: Option, } #[cfg(feature = "v1")] @@ -56,6 +57,7 @@ impl From for Customer { default_payment_method_id: None, updated_by: customer_new.updated_by, version: customer_new.version, + tax_registration_id: customer_new.tax_registration_id, } } } @@ -79,6 +81,7 @@ pub struct CustomerNew { pub default_payment_method_id: Option, pub updated_by: Option, pub version: ApiVersion, + pub tax_registration_id: Option, pub merchant_reference_id: Option, pub default_billing_address: Option, pub default_shipping_address: Option, @@ -109,6 +112,7 @@ impl From for Customer { modified_at: customer_new.modified_at, default_payment_method_id: None, updated_by: customer_new.updated_by, + tax_registration_id: customer_new.tax_registration_id, merchant_reference_id: customer_new.merchant_reference_id, default_billing_address: customer_new.default_billing_address, default_shipping_address: customer_new.default_shipping_address, @@ -140,6 +144,7 @@ pub struct Customer { pub default_payment_method_id: Option, pub updated_by: Option, pub version: ApiVersion, + pub tax_registration_id: Option, } #[cfg(feature = "v2")] @@ -161,6 +166,7 @@ pub struct Customer { pub default_payment_method_id: Option, pub updated_by: Option, pub version: ApiVersion, + pub tax_registration_id: Option, pub merchant_reference_id: Option, pub default_billing_address: Option, pub default_shipping_address: Option, @@ -185,6 +191,7 @@ pub struct CustomerUpdateInternal { pub address_id: Option, pub default_payment_method_id: Option>, pub updated_by: Option, + pub tax_registration_id: Option, } #[cfg(feature = "v1")] @@ -200,6 +207,7 @@ impl CustomerUpdateInternal { connector_customer, address_id, default_payment_method_id, + tax_registration_id, .. } = self; @@ -216,6 +224,7 @@ impl CustomerUpdateInternal { default_payment_method_id: default_payment_method_id .flatten() .map_or(source.default_payment_method_id, Some), + tax_registration_id: tax_registration_id.map_or(source.tax_registration_id, Some), ..source } } @@ -240,6 +249,7 @@ pub struct CustomerUpdateInternal { pub default_billing_address: Option, pub default_shipping_address: Option, pub status: Option, + pub tax_registration_id: Option, } #[cfg(feature = "v2")] @@ -257,6 +267,7 @@ impl CustomerUpdateInternal { default_billing_address, default_shipping_address, status, + tax_registration_id, .. } = self; @@ -277,6 +288,7 @@ impl CustomerUpdateInternal { default_shipping_address: default_shipping_address .map_or(source.default_shipping_address, Some), status: status.unwrap_or(source.status), + tax_registration_id: tax_registration_id.map_or(source.tax_registration_id, Some), ..source } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index bd18e43e31a..2b215ff6724 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -65,6 +65,11 @@ pub struct PaymentIntent { pub is_iframe_redirection_enabled: Option, pub is_payment_id_from_merchant: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, + pub order_date: Option, pub merchant_reference_id: Option, pub billing_address: Option, pub shipping_address: Option, @@ -159,6 +164,11 @@ pub struct PaymentIntent { pub extended_return_url: Option, pub is_payment_id_from_merchant: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, + pub order_date: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, diesel::AsExpression, PartialEq)] @@ -358,6 +368,11 @@ pub struct PaymentIntentNew { pub is_iframe_redirection_enabled: Option, pub is_payment_id_from_merchant: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, + pub order_date: Option, } #[cfg(feature = "v1")] @@ -435,6 +450,11 @@ pub struct PaymentIntentNew { pub extended_return_url: Option, pub is_payment_id_from_merchant: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, } #[cfg(feature = "v2")] @@ -596,6 +616,11 @@ pub struct PaymentIntentUpdateFields { pub force_3ds_challenge: Option, pub is_iframe_redirection_enabled: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, } // TODO: uncomment fields as necessary @@ -755,6 +780,11 @@ impl PaymentIntentUpdateInternal { id: source.id, is_payment_id_from_merchant: source.is_payment_id_from_merchant, payment_channel: source.payment_channel, + tax_status: source.tax_status, + discount_amount: source.discount_amount, + shipping_amount_tax: source.shipping_amount_tax, + duty_amount: source.duty_amount, + order_date: source.order_date, } } } @@ -804,6 +834,11 @@ pub struct PaymentIntentUpdateInternal { pub is_iframe_redirection_enabled: Option, pub extended_return_url: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, } #[cfg(feature = "v1")] @@ -850,6 +885,11 @@ impl PaymentIntentUpdate { is_iframe_redirection_enabled, extended_return_url, payment_channel, + tax_status, + discount_amount, + order_date, + shipping_amount_tax, + duty_amount, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -900,6 +940,11 @@ impl PaymentIntentUpdate { .or(source.is_iframe_redirection_enabled), extended_return_url: extended_return_url.or(source.extended_return_url), payment_channel: payment_channel.or(source.payment_channel), + tax_status: tax_status.or(source.tax_status), + discount_amount: discount_amount.or(source.discount_amount), + order_date: order_date.or(source.order_date), + shipping_amount_tax: shipping_amount_tax.or(source.shipping_amount_tax), + duty_amount: duty_amount.or(source.duty_amount), ..source } } @@ -953,6 +998,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::Update(value) => Self { amount: Some(value.amount), @@ -996,6 +1046,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: value.is_iframe_redirection_enabled, extended_return_url: value.return_url, payment_channel: value.payment_channel, + tax_status: value.tax_status, + discount_amount: value.discount_amount, + order_date: value.order_date, + shipping_amount_tax: value.shipping_amount_tax, + duty_amount: value.duty_amount, }, PaymentIntentUpdate::PaymentCreateUpdate { return_url, @@ -1046,6 +1101,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: return_url, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::PGStatusUpdate { status, @@ -1092,6 +1152,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::MerchantStatusUpdate { status, @@ -1139,6 +1204,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::ResponseUpdate { // amount, @@ -1193,6 +1263,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { active_attempt_id, @@ -1239,6 +1314,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::StatusAndAttemptUpdate { status, @@ -1286,6 +1366,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::ApproveUpdate { status, @@ -1332,6 +1417,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::RejectUpdate { status, @@ -1378,6 +1468,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::SurchargeApplicableUpdate { surcharge_applicable, @@ -1423,6 +1518,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { amount: Some(amount), @@ -1465,6 +1565,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::AuthorizationCountUpdate { authorization_count, @@ -1509,6 +1614,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address_id, @@ -1553,6 +1663,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { status, @@ -1595,6 +1710,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, PaymentIntentUpdate::SessionResponseUpdate { tax_details, @@ -1642,6 +1762,11 @@ impl From for PaymentIntentUpdateInternal { is_iframe_redirection_enabled: None, extended_return_url: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }, } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 76ab2619658..54cec1fb8e2 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -31,6 +31,7 @@ diesel::table! { #[max_length = 32] updated_by -> Varchar, email -> Nullable, + origin_zip -> Nullable, } } @@ -366,6 +367,7 @@ diesel::table! { #[max_length = 64] updated_by -> Nullable, version -> ApiVersion, + tax_registration_id -> Nullable, } } @@ -1071,6 +1073,11 @@ diesel::table! { is_payment_id_from_merchant -> Nullable, #[max_length = 64] payment_channel -> Nullable, + tax_status -> Nullable, + discount_amount -> Nullable, + shipping_amount_tax -> Nullable, + duty_amount -> Nullable, + order_date -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 3f862a8205e..e2b90ce166a 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -31,6 +31,7 @@ diesel::table! { #[max_length = 32] updated_by -> Varchar, email -> Nullable, + origin_zip -> Nullable, } } @@ -371,6 +372,7 @@ diesel::table! { #[max_length = 64] updated_by -> Nullable, version -> ApiVersion, + tax_registration_id -> Nullable, #[max_length = 64] merchant_reference_id -> Nullable, default_billing_address -> Nullable, @@ -1003,6 +1005,11 @@ diesel::table! { is_payment_id_from_merchant -> Nullable, #[max_length = 64] payment_channel -> Nullable, + tax_status -> Nullable, + discount_amount -> Nullable, + shipping_amount_tax -> Nullable, + duty_amount -> Nullable, + order_date -> Nullable, #[max_length = 64] merchant_reference_id -> Nullable, billing_address -> Nullable, diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 8f795d67eab..0cdb553059c 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -39,6 +39,20 @@ pub struct OrderDetailsWithAmount { pub tax_rate: Option, /// total tax amount applicable to the product pub total_tax_amount: Option, + /// description of the product + pub description: Option, + /// stock keeping unit of the product + pub sku: Option, + /// universal product code of the product + pub upc: Option, + /// commodity code of the product + pub commodity_code: Option, + /// unit of measure of the product + pub unit_of_measure: Option, + /// total amount of the product + pub total_amount: Option, + /// discount amount on the unit + pub unit_discount_amount: Option, } impl masking::SerializableSecret for OrderDetailsWithAmount {} diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs index 6bb4cf345f7..952e1be478c 100644 --- a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs @@ -627,6 +627,7 @@ impl From for api_models::payments::AddressDetai line3: item.line3, first_name: None, last_name: None, + origin_zip: None, } } } diff --git a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs index 4aef739b979..bfd4c1c7b59 100644 --- a/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs @@ -442,6 +442,7 @@ impl .and_then(|address| address.postal_code), first_name: None, last_name: None, + origin_zip: None, }), phone: None, email: None, diff --git a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs index 4cf4d2b542f..ffabab2ae95 100644 --- a/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stripebilling/transformers.rs @@ -384,6 +384,7 @@ impl From for api_models::payments::AddressD line3: None, first_name: None, last_name: None, + origin_zip: None, } } } diff --git a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs index c986eeccfda..1c3967aed06 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpayvantiv/transformers.rs @@ -1,4 +1,4 @@ -use common_utils::{ext_traits::Encode, types::MinorUnit}; +use common_utils::{ext_traits::Encode, id_type::CustomerId, types::MinorUnit}; use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, @@ -161,15 +161,25 @@ pub struct Capture { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VantivAddressData { + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub address_line1: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub address_line2: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub address_line3: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub city: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub state: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub zip: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub email: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub country: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub phone: Option>, } @@ -199,6 +209,8 @@ pub struct Authorization { #[serde(skip_serializing_if = "Option::is_none")] pub card: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub enhanced_data: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub processing_type: Option, @@ -233,6 +245,8 @@ pub struct Sale { #[serde(skip_serializing_if = "Option::is_none")] pub card: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub enhanced_data: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, #[serde(skip_serializing_if = "Option::is_none")] pub processing_type: Option, @@ -240,6 +254,71 @@ pub struct Sale { pub original_network_transaction_id: Option>, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EnhancedData { + #[serde(skip_serializing_if = "Option::is_none")] + pub customer_reference: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sales_tax: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tax_exempt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub discount_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub shipping_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub duty_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ship_from_postal_code: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub destination_postal_code: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub destination_country_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub invoice_reference_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line_item_data: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DetailTax { + pub tax_included_in_total: Option, + pub tax_amount: Option, + pub tax_rate: Option, + pub tax_type_identifier: Option, + pub card_acceptor_tax_id: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LineItemData { + #[serde(skip_serializing_if = "Option::is_none")] + pub item_sequence_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub item_description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub product_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub quantity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unit_of_measure: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tax_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line_item_total: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub line_item_total_with_tax: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub item_discount_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub commodity_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub unit_cost: Option, + // pub detail_tax: Option, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RefundRequest { @@ -558,7 +637,9 @@ impl TryFrom<&WorldpayvantivRouterData<&PaymentsAuthorizeRouterData>> for CnpOnl let bill_to_address = get_bill_to_address(item.router_data); let ship_to_address = get_ship_to_address(item.router_data); let processing_info = get_processing_info(&item.router_data.request)?; + let enhanced_data = get_enhanced_data(item.router_data)?; let order_source = OrderSource::from(item); + let (authorization, sale) = if item.router_data.request.is_auto_capture()? && item.amount != MinorUnit::zero() { ( @@ -580,6 +661,7 @@ impl TryFrom<&WorldpayvantivRouterData<&PaymentsAuthorizeRouterData>> for CnpOnl token: processing_info.token, processing_type: processing_info.processing_type, original_network_transaction_id: processing_info.network_transaction_id, + enhanced_data, }), ) } else { @@ -606,6 +688,7 @@ impl TryFrom<&WorldpayvantivRouterData<&PaymentsAuthorizeRouterData>> for CnpOnl processing_type: processing_info.processing_type, original_network_transaction_id: processing_info.network_transaction_id, cardholder_authentication, + enhanced_data, }), None, ) @@ -644,6 +727,59 @@ impl From<&WorldpayvantivRouterData<&PaymentsAuthorizeRouterData>> for OrderSour } } +fn get_enhanced_data( + router_data: &PaymentsAuthorizeRouterData, +) -> Result, error_stack::Report> { + let l2_l3_data = router_data.l2_l3_data.clone(); + if let Some(l2_l3_data) = l2_l3_data { + let line_item_data = l2_l3_data.order_details.map(|order_details| { + order_details + .iter() + .enumerate() + .map(|(i, order)| LineItemData { + item_sequence_number: Some((i + 1).to_string()), + item_description: order + .description + .as_ref() + .map(|desc| desc.chars().take(19).collect::()), + product_code: order.product_id.clone(), + quantity: Some(order.quantity.to_string().clone()), + unit_of_measure: order.unit_of_measure.clone(), + tax_amount: order.total_tax_amount, + line_item_total: Some(order.amount), + line_item_total_with_tax: order.total_tax_amount.map(|tax| tax + order.amount), + item_discount_amount: order.unit_discount_amount, + commodity_code: order.commodity_code.clone(), + unit_cost: Some(order.amount), + }) + .collect() + }); + + let tax_exempt = match l2_l3_data.tax_status { + Some(common_enums::TaxStatus::Exempt) => Some(true), + Some(common_enums::TaxStatus::Taxable) => Some(false), + None => None, + }; + + let enhanced_data = EnhancedData { + customer_reference: l2_l3_data.customer_id, + sales_tax: l2_l3_data.order_tax_amount, + tax_exempt, + discount_amount: l2_l3_data.discount_amount, + shipping_amount: l2_l3_data.shipping_cost, + duty_amount: l2_l3_data.duty_amount, + 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, + line_item_data, + }; + Ok(Some(enhanced_data)) + } else { + Ok(None) + } +} + fn get_processing_info( request: &PaymentsAuthorizeData, ) -> Result> { diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 7222acddc37..172c13722bb 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -6270,6 +6270,7 @@ pub(crate) fn convert_payment_authorize_router_response( psd2_sca_exemption_type: data.psd2_sca_exemption_type, raw_connector_response: data.raw_connector_response.clone(), is_payment_id_from_merchant: data.is_payment_id_from_merchant, + l2_l3_data: data.l2_l3_data.clone(), } } diff --git a/crates/hyperswitch_domain_models/src/address.rs b/crates/hyperswitch_domain_models/src/address.rs index f8e38ae8a35..a816deab7cc 100644 --- a/crates/hyperswitch_domain_models/src/address.rs +++ b/crates/hyperswitch_domain_models/src/address.rs @@ -50,6 +50,7 @@ pub struct AddressDetails { pub state: Option>, pub first_name: Option>, pub last_name: Option>, + pub origin_zip: Option>, } impl AddressDetails { @@ -88,6 +89,7 @@ impl AddressDetails { line3: self.line3.clone().or(other.line3.clone()), zip: self.zip.clone().or(other.zip.clone()), state: self.state.clone().or(other.state.clone()), + origin_zip: self.origin_zip.clone().or(other.origin_zip.clone()), } } else { self.clone() @@ -123,6 +125,7 @@ impl From for AddressDetails { state: address.state, first_name: address.first_name, last_name: address.last_name, + origin_zip: address.origin_zip, } } } @@ -160,6 +163,7 @@ impl From for api_models::payments::AddressDetails { state: address.state, first_name: address.first_name, last_name: address.last_name, + origin_zip: address.origin_zip, } } } diff --git a/crates/hyperswitch_domain_models/src/bulk_tokenization.rs b/crates/hyperswitch_domain_models/src/bulk_tokenization.rs index 0eff34a1bf5..ccefd3e34e1 100644 --- a/crates/hyperswitch_domain_models/src/bulk_tokenization.rs +++ b/crates/hyperswitch_domain_models/src/bulk_tokenization.rs @@ -78,7 +78,8 @@ pub struct CardNetworkTokenizeRecord { pub customer_phone: Option>, #[serde(rename = "phone_country_code")] pub customer_phone_country_code: Option, - + #[serde(rename = "tax_registration_id")] + pub customer_tax_registration_id: Option>, // Billing details pub billing_address_city: Option, pub billing_address_country: Option, @@ -106,6 +107,7 @@ impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::CustomerDetails { email: record.customer_email.clone(), phone: record.customer_phone.clone(), phone_country_code: record.customer_phone_country_code.clone(), + tax_registration_id: record.customer_tax_registration_id.clone(), } } } @@ -123,6 +125,7 @@ impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::Address { zip: record.billing_address_zip.clone(), state: record.billing_address_state.clone(), country: record.billing_address_country, + origin_zip: None, }), phone: Some(payments_api::PhoneDetails { number: record.billing_phone_number.clone(), @@ -217,6 +220,7 @@ impl ForeignTryFrom for payments_api::CustomerDetails { email: customer.email, phone: customer.phone, phone_country_code: customer.phone_country_code, + tax_registration_id: customer.tax_registration_id, }) } } @@ -268,6 +272,7 @@ impl ForeignFrom for CustomerDetails { email: req.email, phone: req.phone, phone_country_code: req.phone_country_code, + tax_registration_id: req.tax_registration_id, } } } @@ -294,6 +299,7 @@ impl ForeignFrom for AddressDetails { state: req.state, first_name: req.first_name, last_name: req.last_name, + origin_zip: req.origin_zip, } } } diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index fce8a2c31ff..5a88cb6cd48 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -45,6 +45,8 @@ pub struct Customer { pub default_payment_method_id: Option, pub updated_by: Option, pub version: common_enums::ApiVersion, + #[encrypt] + pub tax_registration_id: Option>>, } #[cfg(feature = "v2")] @@ -71,6 +73,8 @@ pub struct Customer { pub id: id_type::GlobalCustomerId, pub version: common_enums::ApiVersion, pub status: DeleteStatus, + #[encrypt] + pub tax_registration_id: Option>>, } impl Customer { @@ -140,6 +144,7 @@ impl behaviour::Conversion for Customer { default_payment_method_id: self.default_payment_method_id, updated_by: self.updated_by, version: self.version, + tax_registration_id: self.tax_registration_id.map(Encryption::from), }) } @@ -160,6 +165,7 @@ impl behaviour::Conversion for Customer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), + tax_registration_id: item.tax_registration_id.clone(), }, )), keymanager::Identifier::Merchant(item.merchant_id.clone()), @@ -198,6 +204,7 @@ impl behaviour::Conversion for Customer { default_payment_method_id: item.default_payment_method_id, updated_by: item.updated_by, version: item.version, + tax_registration_id: encryptable_customer.tax_registration_id, }) } @@ -218,6 +225,7 @@ impl behaviour::Conversion for Customer { address_id: self.address_id, updated_by: self.updated_by, version: self.version, + tax_registration_id: self.tax_registration_id.map(Encryption::from), }) } } @@ -247,6 +255,7 @@ impl behaviour::Conversion for Customer { default_shipping_address: self.default_shipping_address, version: self.version, status: self.status, + tax_registration_id: self.tax_registration_id.map(Encryption::from), }) } @@ -267,6 +276,7 @@ impl behaviour::Conversion for Customer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), + tax_registration_id: item.tax_registration_id.clone(), }, )), keymanager::Identifier::Merchant(item.merchant_id.clone()), @@ -308,6 +318,7 @@ impl behaviour::Conversion for Customer { default_shipping_address: item.default_shipping_address, version: item.version, status: item.status, + tax_registration_id: encryptable_customer.tax_registration_id, }) } @@ -332,6 +343,7 @@ impl behaviour::Conversion for Customer { default_shipping_address: self.default_shipping_address, version: common_types::consts::API_VERSION, status: self.status, + tax_registration_id: self.tax_registration_id.map(Encryption::from), }) } } @@ -350,6 +362,7 @@ pub struct CustomerGeneralUpdate { pub default_shipping_address: Option, pub default_payment_method_id: Option>, pub status: Option, + pub tax_registration_id: crypto::OptionalEncryptableSecretString, } #[cfg(feature = "v2")] @@ -381,6 +394,7 @@ impl From for CustomerUpdateInternal { default_shipping_address, default_payment_method_id, status, + tax_registration_id, } = *update; Self { name: name.map(Encryption::from), @@ -396,6 +410,7 @@ impl From for CustomerUpdateInternal { default_payment_method_id, updated_by: None, status, + tax_registration_id: tax_registration_id.map(Encryption::from), } } CustomerUpdate::ConnectorCustomer { connector_customer } => Self { @@ -412,6 +427,7 @@ impl From for CustomerUpdateInternal { default_billing_address: None, default_shipping_address: None, status: None, + tax_registration_id: None, }, CustomerUpdate::UpdateDefaultPaymentMethod { default_payment_method_id, @@ -429,6 +445,7 @@ impl From for CustomerUpdateInternal { default_billing_address: None, default_shipping_address: None, status: None, + tax_registration_id: None, }, } } @@ -443,9 +460,10 @@ pub enum CustomerUpdate { phone: Box, description: Option, phone_country_code: Option, - metadata: Option, + metadata: Box>, connector_customer: Box>, address_id: Option, + tax_registration_id: crypto::OptionalEncryptableSecretString, }, ConnectorCustomer { connector_customer: Option, @@ -468,18 +486,20 @@ impl From for CustomerUpdateInternal { metadata, connector_customer, address_id, + tax_registration_id, } => Self { name: name.map(Encryption::from), email: email.map(Encryption::from), phone: phone.map(Encryption::from), description, phone_country_code, - metadata, + metadata: *metadata, connector_customer: *connector_customer, modified_at: date_time::now(), address_id, default_payment_method_id: None, updated_by: None, + tax_registration_id: tax_registration_id.map(Encryption::from), }, CustomerUpdate::ConnectorCustomer { connector_customer } => Self { connector_customer, @@ -493,6 +513,7 @@ impl From for CustomerUpdateInternal { default_payment_method_id: None, updated_by: None, address_id: None, + tax_registration_id: None, }, CustomerUpdate::UpdateDefaultPaymentMethod { default_payment_method_id, @@ -508,6 +529,7 @@ impl From for CustomerUpdateInternal { connector_customer: None, updated_by: None, address_id: None, + tax_registration_id: None, }, } } diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 30d0913a716..dac5aa13410 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -401,6 +401,13 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW product_tax_code, tax_rate, total_tax_amount, + description, + sku, + upc, + commodity_code, + unit_of_measure, + total_amount, + unit_discount_amount, } = from; Self { product_name, @@ -416,6 +423,13 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW product_tax_code, tax_rate, total_tax_amount, + description, + sku, + upc, + commodity_code, + unit_of_measure, + total_amount, + unit_discount_amount, } } @@ -434,6 +448,13 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW product_tax_code, tax_rate, total_tax_amount, + description, + sku, + upc, + commodity_code, + unit_of_measure, + total_amount, + unit_discount_amount, } = self; ApiOrderDetailsWithAmount { product_name, @@ -449,6 +470,13 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW product_tax_code, tax_rate, total_tax_amount, + description, + sku, + upc, + commodity_code, + unit_of_measure, + total_amount, + unit_discount_amount, } } } diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index 264d16c95d6..ff4d8bc0ae8 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -1020,8 +1020,10 @@ impl TryFrom<(payment_methods::PaymentMethodRecord, id_type::MerchantId)> zip: record.billing_address_zip, first_name: record.billing_address_first_name, last_name: record.billing_address_last_name, + origin_zip: None, }), metadata: None, + tax_registration_id: None, }, connector_customer_details, }) diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 62f4dbf8ac9..2a6f33ed7ae 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -117,6 +117,11 @@ pub struct PaymentIntent { pub is_iframe_redirection_enabled: Option, pub is_payment_id_from_merchant: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, } impl PaymentIntent { diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 04e3adf0bdd..7d7e08f8038 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -167,6 +167,7 @@ pub struct CustomerData { pub email: Option, pub phone: Option>, pub phone_country_code: Option, + pub tax_registration_id: Option>, } #[cfg(feature = "v2")] @@ -241,6 +242,11 @@ pub struct PaymentIntentUpdateFields { pub tax_details: Option, pub force_3ds_challenge: Option, pub is_iframe_redirection_enabled: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, pub is_confirm_operation: bool, pub payment_channel: Option, } @@ -430,6 +436,11 @@ pub struct PaymentIntentUpdateInternal { pub force_3ds_challenge: Option, pub is_iframe_redirection_enabled: Option, pub payment_channel: Option, + pub tax_status: Option, + pub discount_amount: Option, + pub order_date: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, } // This conversion is used in the `update_payment_intent` function @@ -830,6 +841,12 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: value.merchant_order_reference_id, shipping_details: value.shipping_details, is_payment_processor_token_flow: value.is_payment_processor_token_flow, + tax_details: value.tax_details, + tax_status: value.tax_status, + discount_amount: value.discount_amount, + order_date: value.order_date, + shipping_amount_tax: value.shipping_amount_tax, + duty_amount: value.duty_amount, ..Default::default() }, PaymentIntentUpdate::PaymentCreateUpdate { @@ -1062,6 +1079,11 @@ impl From for DieselPaymentIntentUpdate { force_3ds_challenge: value.force_3ds_challenge, is_iframe_redirection_enabled: value.is_iframe_redirection_enabled, payment_channel: value.payment_channel, + tax_status: value.tax_status, + discount_amount: value.discount_amount, + order_date: value.order_date, + shipping_amount_tax: value.shipping_amount_tax, + duty_amount: value.duty_amount, })) } PaymentIntentUpdate::PaymentCreateUpdate { @@ -1221,6 +1243,11 @@ impl From for diesel_models::PaymentIntentUpdateInt force_3ds_challenge, is_iframe_redirection_enabled, payment_channel, + tax_status, + discount_amount, + order_date, + shipping_amount_tax, + duty_amount, } = value; Self { amount, @@ -1263,6 +1290,11 @@ impl From for diesel_models::PaymentIntentUpdateInt is_iframe_redirection_enabled, extended_return_url: return_url, payment_channel, + tax_status, + discount_amount, + order_date, + shipping_amount_tax, + duty_amount, } } } @@ -1733,6 +1765,11 @@ impl behaviour::Conversion for PaymentIntent { is_iframe_redirection_enabled, is_payment_id_from_merchant, payment_channel: None, + tax_status: None, + discount_amount: None, + shipping_amount_tax: None, + duty_amount: None, + order_date: None, }) } async fn convert_back( @@ -1964,6 +2001,11 @@ impl behaviour::Conversion for PaymentIntent { routing_algorithm_id: self.routing_algorithm_id, is_payment_id_from_merchant: self.is_payment_id_from_merchant, payment_channel: None, + tax_status: None, + discount_amount: None, + shipping_amount_tax: None, + duty_amount: None, + order_date: None, }) } } @@ -2040,6 +2082,11 @@ impl behaviour::Conversion for PaymentIntent { extended_return_url: self.return_url, is_payment_id_from_merchant: self.is_payment_id_from_merchant, payment_channel: self.payment_channel, + tax_status: self.tax_status, + discount_amount: self.discount_amount, + order_date: self.order_date, + shipping_amount_tax: self.shipping_amount_tax, + duty_amount: self.duty_amount, }) } @@ -2142,6 +2189,11 @@ impl behaviour::Conversion for PaymentIntent { is_iframe_redirection_enabled: storage_model.is_iframe_redirection_enabled, is_payment_id_from_merchant: storage_model.is_payment_id_from_merchant, payment_channel: storage_model.payment_channel, + tax_status: storage_model.tax_status, + discount_amount: storage_model.discount_amount, + shipping_amount_tax: storage_model.shipping_amount_tax, + duty_amount: storage_model.duty_amount, + order_date: storage_model.order_date, }) } .await @@ -2216,6 +2268,11 @@ impl behaviour::Conversion for PaymentIntent { extended_return_url: self.return_url, is_payment_id_from_merchant: self.is_payment_id_from_merchant, payment_channel: self.payment_channel, + tax_status: self.tax_status, + discount_amount: self.discount_amount, + order_date: self.order_date, + shipping_amount_tax: self.shipping_amount_tax, + duty_amount: self.duty_amount, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index c77f0e3a7bb..f0d51e1d9fc 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -99,6 +99,8 @@ pub struct RouterData { pub connector_mandate_request_reference_id: Option, + pub l2_l3_data: Option, + pub authentication_id: Option, /// Contains the type of sca exemption required for the transaction pub psd2_sca_exemption_type: Option, @@ -111,6 +113,26 @@ pub struct RouterData { pub is_payment_id_from_merchant: Option, } +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +pub struct L2L3Data { + pub order_date: Option, + pub tax_status: Option, + pub customer_tax_registration_id: Option>, + pub order_details: Option>, + pub discount_amount: Option, + pub shipping_cost: Option, + pub shipping_amount_tax: Option, + pub duty_amount: Option, + pub order_tax_amount: Option, + pub merchant_order_reference_id: Option, + pub customer_id: Option, + pub shipping_origin_zip: Option>, + pub shipping_state: Option>, + pub shipping_country: Option, + pub shipping_destination_zip: Option>, + pub billing_address_city: Option, +} + // Different patterns of authentication. #[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(tag = "auth_type")] diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index c33603ab279..c32b7b9ab02 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -948,6 +948,7 @@ pub struct CustomerDetails { pub email: Option, pub phone: Option>, pub phone_country_code: Option, + pub tax_registration_id: Option>, } #[derive(Debug, Clone)] diff --git a/crates/hyperswitch_interfaces/src/conversion_impls.rs b/crates/hyperswitch_interfaces/src/conversion_impls.rs index 6e027b88363..93c7d5c2cff 100644 --- a/crates/hyperswitch_interfaces/src/conversion_impls.rs +++ b/crates/hyperswitch_interfaces/src/conversion_impls.rs @@ -86,6 +86,7 @@ fn get_default_router_data( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 344036a198f..7c5126c0f90 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -337,6 +337,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::Connector, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodIssuerCode, + api_models::enums::TaxStatus, api_models::enums::MandateStatus, api_models::enums::PaymentExperience, api_models::enums::BankNames, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 9cf0c6f8b76..81a4e431bd4 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -312,6 +312,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::AuthorizationStatus, api_models::enums::ElementPosition, api_models::enums::ElementSize, + api_models::enums::TaxStatus, api_models::enums::SizeVariants, api_models::enums::MerchantProductType, api_models::enums::PaymentLinkDetailsLayout, diff --git a/crates/router/src/compatibility/stripe/customers/types.rs b/crates/router/src/compatibility/stripe/customers/types.rs index 555ed4e992e..acfe8a2fb5c 100644 --- a/crates/router/src/compatibility/stripe/customers/types.rs +++ b/crates/router/src/compatibility/stripe/customers/types.rs @@ -116,6 +116,7 @@ impl From for payments::AddressDetails { first_name: None, line3: None, last_name: None, + origin_zip: None, } } } diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index acf2f5e03ab..2bdfde27103 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -54,6 +54,7 @@ impl From for payments::Address { first_name: None, line3: None, last_name: None, + origin_zip: None, }), } } @@ -204,6 +205,7 @@ impl From for payments::Address { first_name: details.name, line3: None, last_name: None, + origin_zip: None, }), } } diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 4f53a2ef2df..1ed4f1b37c7 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -539,6 +539,7 @@ pub(crate) async fn fetch_raw_secrets( network_tokenization_supported_connectors: conf.network_tokenization_supported_connectors, theme: conf.theme, platform: conf.platform, + l2_l3_data_config: conf.l2_l3_data_config, authentication_providers: conf.authentication_providers, open_router: conf.open_router, #[cfg(feature = "v2")] diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 1604b7eaa9d..43de622ccfb 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -117,6 +117,7 @@ pub struct Settings { #[cfg(feature = "payouts")] pub payouts: Payouts, pub payout_method_filters: ConnectorFilters, + pub l2_l3_data_config: L2L3DataConfig, pub debit_routing_config: DebitRoutingConfig, pub applepay_decrypt_keys: SecretStateContainer, pub paze_decrypt_keys: Option>, @@ -319,6 +320,10 @@ impl TenantConfig { .collect() } } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct L2L3DataConfig { + pub enabled: bool, +} #[derive(Debug, Clone)] pub struct Tenant { diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 843a8988dff..7df31b85537 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -208,6 +208,7 @@ pub fn construct_router_data( psd2_sca_exemption_type, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }) } diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 43dbf28c0e0..ffc44781037 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -160,6 +160,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { name: self.name.clone(), email: self.email.clone().map(|a| a.expose().switch_strategy()), phone: self.phone.clone(), + tax_registration_id: self.tax_registration_id.clone(), }, ), ), @@ -215,6 +216,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { default_payment_method_id: None, updated_by: None, version: common_types::consts::API_VERSION, + tax_registration_id: encryptable_customer.tax_registration_id, }) } @@ -286,6 +288,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { name: Some(self.name.clone()), email: Some(self.email.clone().expose().switch_strategy()), phone: self.phone.clone(), + tax_registration_id: self.tax_registration_id.clone(), }, ), ), @@ -344,6 +347,7 @@ impl CustomerCreateBridge for customers::CustomerRequest { default_shipping_address: encrypted_customer_shipping_address.map(Into::into), version: common_types::consts::API_VERSION, status: common_enums::DeleteStatus::Active, + tax_registration_id: encryptable_customer.tax_registration_id, }) } @@ -750,6 +754,7 @@ impl CustomerDeleteBridge for id_type::GlobalCustomerId { default_shipping_address: None, default_payment_method_id: None, status: Some(common_enums::DeleteStatus::Redacted), + tax_registration_id: Some(redacted_encrypted_value), })); db.update_customer_by_global_id( @@ -955,6 +960,7 @@ impl CustomerDeleteBridge for id_type::CustomerId { .storage_scheme .to_string(), email: Some(redacted_encrypted_email), + origin_zip: Some(redacted_encrypted_value.clone()), }; match db @@ -996,9 +1002,10 @@ impl CustomerDeleteBridge for id_type::CustomerId { phone: Box::new(Some(redacted_encrypted_value.clone())), description: Some(Description::from_str_unchecked(REDACTED)), phone_country_code: Some(REDACTED.to_string()), - metadata: None, + metadata: Box::new(None), connector_customer: Box::new(None), address_id: None, + tax_registration_id: Some(redacted_encrypted_value.clone()), }; db.update_customer_by_customer_id_merchant_id( @@ -1287,6 +1294,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .as_ref() .map(|a| a.clone().expose().switch_strategy()), phone: self.phone.clone(), + tax_registration_id: self.tax_registration_id.clone(), }, ), ), @@ -1323,8 +1331,9 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { encryptable }), phone: Box::new(encryptable_customer.phone), + tax_registration_id: encryptable_customer.tax_registration_id, phone_country_code: self.phone_country_code.clone(), - metadata: self.metadata.clone(), + metadata: Box::new(self.metadata.clone()), description: self.description.clone(), connector_customer: Box::new(None), address_id: address.clone().map(|addr| addr.address_id), @@ -1409,6 +1418,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .as_ref() .map(|a| a.clone().expose().switch_strategy()), phone: self.phone.clone(), + tax_registration_id: self.tax_registration_id.clone(), }, ), ), @@ -1444,6 +1454,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { encryptable })), phone: Box::new(encryptable_customer.phone), + tax_registration_id: encryptable_customer.tax_registration_id, phone_country_code: self.phone_country_code.clone(), metadata: self.metadata.clone(), description: self.description.clone(), diff --git a/crates/router/src/core/files.rs b/crates/router/src/core/files.rs index 2a22414f646..b43ed946619 100644 --- a/crates/router/src/core/files.rs +++ b/crates/router/src/core/files.rs @@ -51,14 +51,15 @@ pub async fn files_create_core( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to insert file_metadata")?; - let (provider_file_id, file_upload_provider, profile_id, merchant_connector_id) = + let (provider_file_id, file_upload_provider, profile_id, merchant_connector_id) = Box::pin( helpers::upload_and_get_provider_provider_file_id_profile_id( &state, &merchant_context, &create_file_request, file_key.clone(), - ) - .await?; + ), + ) + .await?; // Update file metadata let update_file_metadata = diesel_models::file::FileMetadataUpdate::Update { diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index 95bf1994003..560cc97d634 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -160,6 +160,7 @@ impl ConstructFlowSpecificData( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index 7823e8db13b..d1fd0410f74 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -131,6 +131,7 @@ impl ConstructFlowSpecificData { email: customer.email.clone().map(Email::from), phone: customer.phone.clone().map(|phone| phone.into_inner()), phone_country_code: customer.phone_country_code.clone(), + tax_registration_id: customer + .tax_registration_id + .clone() + .map(|tax_registration_id| tax_registration_id.into_inner()), })) }, ) @@ -387,6 +391,7 @@ impl CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest> { .clone() .map(|email| email.expose().switch_strategy()), phone: self.customer.phone.clone(), + tax_registration_id: self.customer.tax_registration_id.clone(), }, )), Identifier::Merchant(self.merchant_account.get_id().clone()), @@ -425,6 +430,7 @@ impl CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest> { default_payment_method_id: None, updated_by: None, version: common_types::consts::API_VERSION, + tax_registration_id: encryptable_customer.tax_registration_id, }; db.insert_customer( @@ -450,6 +456,7 @@ impl CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest> { email: self.customer.email.clone(), phone: self.customer.phone.clone(), phone_country_code: self.customer.phone_country_code.clone(), + tax_registration_id: self.customer.tax_registration_id.clone(), }) } diff --git a/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs b/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs index 9f9cfe714b3..ca9c2d9d140 100644 --- a/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs +++ b/crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs @@ -374,6 +374,10 @@ impl CardNetworkTokenizeExecutor<'_, domain::TokenizePaymentMethodRequest> { email: customer.email.clone().map(Email::from), phone: customer.phone.clone().map(|phone| phone.into_inner()), phone_country_code: customer.phone_country_code.clone(), + tax_registration_id: customer + .tax_registration_id + .clone() + .map(|tax_registration_id| tax_registration_id.into_inner()), }; Ok((locker_id, customer_details)) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 0e7ccdcac8e..db1a9713f2d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -149,6 +149,10 @@ pub async fn create_or_update_address_for_payment_by_request( .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address + .address + .as_ref() + .and_then(|a| a.origin_zip.clone()), }, ), ), @@ -190,6 +194,7 @@ pub async fn create_or_update_address_for_payment_by_request( ); encryptable }), + origin_zip: encryptable_address.origin_zip, }; let address = db .find_address_by_merchant_id_payment_id_address_id( @@ -359,6 +364,7 @@ pub async fn get_domain_address( .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address.address.as_ref().and_then(|a| a.origin_zip.clone()), }, ), ), @@ -395,6 +401,7 @@ pub async fn get_domain_address( ); encryptable }), + origin_zip: encryptable_address.origin_zip, }) } .await @@ -1583,12 +1590,18 @@ pub fn get_customer_details_from_request( .and_then(|customer_details| customer_details.phone_country_code.clone()) .or(request.phone_country_code.clone()); + let tax_registration_id = request + .customer + .as_ref() + .and_then(|customer_details| customer_details.tax_registration_id.clone()); + CustomerDetails { customer_id, name: customer_name, email: customer_email, phone: customer_phone, phone_country_code: customer_phone_code, + tax_registration_id, } } @@ -1659,12 +1672,14 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( || request_customer_details.email.is_some() || request_customer_details.phone.is_some() || request_customer_details.phone_country_code.is_some() + || request_customer_details.tax_registration_id.is_some() { Some(CustomerData { name: request_customer_details.name.clone(), email: request_customer_details.email.clone(), phone: request_customer_details.phone.clone(), phone_country_code: request_customer_details.phone_country_code.clone(), + tax_registration_id: request_customer_details.tax_registration_id.clone(), }) } else { None @@ -1702,6 +1717,10 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( .phone_country_code .clone() .or(parsed_customer_data.phone_country_code.clone()), + tax_registration_id: request_customer_details + .tax_registration_id + .clone() + .or(parsed_customer_data.tax_registration_id.clone()), }) .or(temp_customer_data); let key_manager_state = state.into(); @@ -1744,6 +1763,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( .as_ref() .map(|e| e.clone().expose().switch_strategy()), phone: request_customer_details.phone.clone(), + tax_registration_id: None, }, ), ), @@ -1765,6 +1785,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( | request_customer_details.name.is_some() | request_customer_details.phone.is_some() | request_customer_details.phone_country_code.is_some() + | request_customer_details.tax_registration_id.is_some() { let customer_update = Update { name: encryptable_customer.name, @@ -1781,8 +1802,9 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( phone_country_code: request_customer_details.phone_country_code, description: None, connector_customer: Box::new(None), - metadata: None, + metadata: Box::new(None), address_id: None, + tax_registration_id: encryptable_customer.tax_registration_id, }; db.update_customer_by_customer_id_merchant_id( @@ -1824,6 +1846,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( default_payment_method_id: None, updated_by: None, version: common_types::consts::API_VERSION, + tax_registration_id: encryptable_customer.tax_registration_id, }; metrics::CUSTOMER_CREATED.add(1, &[]); db.insert_customer(new_customer, key_manager_state, key_store, storage_scheme) @@ -3869,6 +3892,11 @@ mod tests { is_iframe_redirection_enabled: None, is_payment_id_from_merchant: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_ok()); @@ -3947,6 +3975,11 @@ mod tests { is_iframe_redirection_enabled: None, is_payment_id_from_merchant: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent,).is_err()) @@ -4023,6 +4056,11 @@ mod tests { is_iframe_redirection_enabled: None, is_payment_id_from_merchant: None, payment_channel: None, + tax_status: None, + discount_amount: None, + order_date: None, + shipping_amount_tax: None, + duty_amount: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_err()) @@ -4358,6 +4396,7 @@ pub fn router_data_type_conversion( psd2_sca_exemption_type: router_data.psd2_sca_exemption_type, raw_connector_response: router_data.raw_connector_response, is_payment_id_from_merchant: router_data.is_payment_id_from_merchant, + l2_l3_data: router_data.l2_l3_data, } } diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 38a189e50fd..f22e92c1129 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -371,6 +371,7 @@ impl GetTracker, api::PaymentsRequest> email: request.email.clone(), phone: request.phone.clone(), phone_country_code: request.phone_country_code.clone(), + tax_registration_id: None, }); let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 10c93a064b1..55d95cc6dfc 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -2055,6 +2055,11 @@ impl UpdateTracker, api::PaymentsRequest> for .is_iframe_redirection_enabled, is_confirm_operation: true, // Indicates that this is a confirm operation payment_channel: payment_data.payment_intent.payment_channel, + tax_status: payment_data.payment_intent.tax_status, + discount_amount: payment_data.payment_intent.discount_amount, + order_date: payment_data.payment_intent.order_date, + shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax, + duty_amount: payment_data.payment_intent.duty_amount, })), &m_key_store, storage_scheme, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 7d1d32135a2..01eb94cdad3 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1465,6 +1465,7 @@ impl PaymentCreate { phone: request.phone.clone(), email: request.email.clone(), phone_country_code: request.phone_country_code.clone(), + tax_registration_id: None, }) } else { None @@ -1622,6 +1623,11 @@ impl PaymentCreate { .or(business_profile.is_iframe_redirection_enabled), is_payment_id_from_merchant: Some(is_payment_id_from_merchant), payment_channel: request.payment_channel.clone(), + order_date: request.order_date, + discount_amount: request.discount_amount, + duty_amount: request.duty_amount, + tax_status: request.tax_status, + shipping_amount_tax: request.shipping_amount_tax, }) } diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 8a7c22376a0..ba443417cd2 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -134,6 +134,7 @@ impl GetTracker, api::PaymentsSessionR email: None, phone: None, phone_country_code: None, + tax_registration_id: None, }; let creds_identifier = request diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index a0dae81a5fe..0d3f2844bf7 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -949,6 +949,11 @@ impl UpdateTracker, api::PaymentsRequest> for .is_iframe_redirection_enabled, is_confirm_operation: false, // this is not a confirm operation payment_channel: payment_data.payment_intent.payment_channel, + tax_status: payment_data.payment_intent.tax_status, + discount_amount: payment_data.payment_intent.discount_amount, + order_date: payment_data.payment_intent.order_date, + shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax, + duty_amount: payment_data.payment_intent.duty_amount, })), key_store, storage_scheme, @@ -977,6 +982,9 @@ impl ForeignTryFrom for CustomerData { email: value.email.map(Email::from), phone: value.phone.map(|ph| ph.into_inner()), phone_country_code: value.phone_country_code, + tax_registration_id: value + .tax_registration_id + .map(|tax_registration_id| tax_registration_id.into_inner()), }) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index dd9b41ee9d2..faf7f93f285 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -175,6 +175,7 @@ where psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: payment_data.payment_intent.is_payment_id_from_merchant, + l2_l3_data: None, }; Ok(router_data) } @@ -407,6 +408,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: payment_data.payment_intent.is_payment_id_from_merchant, + l2_l3_data: None, }; Ok(router_data) @@ -572,6 +574,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( authentication_id: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -700,6 +703,7 @@ pub async fn construct_router_data_for_psync<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -883,6 +887,7 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( authentication_id: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -1102,6 +1107,7 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -1228,7 +1234,71 @@ where .connector_mandate_detail .as_ref() .and_then(|detail| detail.get_connector_mandate_request_reference_id()); - + let order_details = payment_data + .payment_intent + .order_details + .as_ref() + .map(|order_details| { + order_details + .iter() + .map(|data| { + data.to_owned() + .parse_value("OrderDetailsWithAmount") + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "OrderDetailsWithAmount", + }) + .attach_printable("Unable to parse OrderDetailsWithAmount") + }) + .collect::, _>>() + }) + .transpose()?; + 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(); + + types::L2L3Data { + order_date: payment_data.payment_intent.order_date, + tax_status: payment_data.payment_intent.tax_status, + customer_tax_registration_id: customer.as_ref().and_then(|c| { + c.tax_registration_id + .as_ref() + .map(|e| e.clone().into_inner()) + }), + order_details: order_details.clone(), + discount_amount: payment_data.payment_intent.discount_amount, + shipping_cost: payment_data.payment_intent.shipping_cost, + shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax, + duty_amount: payment_data.payment_intent.duty_amount, + order_tax_amount: payment_data + .payment_attempt + .net_amount + .get_order_tax_amount(), + merchant_order_reference_id: payment_data + .payment_intent + .merchant_order_reference_id + .clone(), + customer_id: payment_data.payment_intent.customer_id.clone(), + shipping_origin_zip: shipping_address + .and_then(|addr| addr.address.as_ref()) + .and_then(|details| details.origin_zip.clone()), + shipping_state: shipping_address + .as_ref() + .and_then(|addr| addr.address.as_ref()) + .and_then(|details| details.state.clone()), + shipping_country: shipping_address + .as_ref() + .and_then(|addr| addr.address.as_ref()) + .and_then(|details| details.country), + shipping_destination_zip: shipping_address + .as_ref() + .and_then(|addr| addr.address.as_ref()) + .and_then(|details| details.zip.clone()), + billing_address_city: billing_address + .as_ref() + .and_then(|addr| addr.address.as_ref()) + .and_then(|details| details.city.clone()), + } + }); crate::logger::debug!("unified address details {:?}", unified_address); router_data = types::RouterData { @@ -1304,6 +1374,7 @@ where psd2_sca_exemption_type: payment_data.payment_intent.psd2_sca_exemption_type, raw_connector_response: None, is_payment_id_from_merchant: payment_data.payment_intent.is_payment_id_from_merchant, + l2_l3_data, }; Ok(router_data) @@ -1497,6 +1568,7 @@ pub async fn construct_payment_router_data_for_update_metadata<'a>( psd2_sca_exemption_type: payment_data.payment_intent.psd2_sca_exemption_type, raw_connector_response: None, is_payment_id_from_merchant: payment_data.payment_intent.is_payment_id_from_merchant, + l2_l3_data: None, }; Ok(router_data) @@ -4980,6 +5052,7 @@ impl ForeignFrom for router_request_types::CustomerDetails { email: customer.email, phone: customer.phone, phone_country_code: customer.phone_country_code, + tax_registration_id: customer.tax_registration_id, } } } diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index bd0014d205e..66a824fc01b 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -629,9 +629,14 @@ pub async fn payouts_cancel_core( .attach_printable("Connector not found for payout cancellation")?, }; - cancel_payout(&state, &merchant_context, &connector_data, &mut payout_data) - .await - .attach_printable("Payout cancellation failed for given Payout request")?; + Box::pin(cancel_payout( + &state, + &merchant_context, + &connector_data, + &mut payout_data, + )) + .await + .attach_printable("Payout cancellation failed for given Payout request")?; } Ok(services::ApplicationResponse::Json( @@ -1088,7 +1093,13 @@ pub async fn call_connector_payout( ); } // Eligibility flow - complete_payout_eligibility(state, merchant_context, connector_data, payout_data).await?; + Box::pin(complete_payout_eligibility( + state, + merchant_context, + connector_data, + payout_data, + )) + .await?; // Create customer flow Box::pin(complete_create_recipient( state, @@ -1380,9 +1391,14 @@ pub async fn complete_payout_eligibility( .connector_name .supports_payout_eligibility(payout_data.payouts.payout_type) { - check_payout_eligibility(state, merchant_context, connector_data, payout_data) - .await - .attach_printable("Eligibility failed for given Payout request")?; + Box::pin(check_payout_eligibility( + state, + merchant_context, + connector_data, + payout_data, + )) + .await + .attach_printable("Eligibility failed for given Payout request")?; } utils::when( diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 8a7f87fa60d..d74baa2011d 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -767,6 +767,7 @@ pub(super) async fn get_or_create_customer_details( .clone() .map(|a| a.expose().switch_strategy()), phone: customer_details.phone.clone(), + tax_registration_id: customer_details.tax_registration_id.clone(), }, ), ), @@ -810,6 +811,7 @@ pub(super) async fn get_or_create_customer_details( default_payment_method_id: None, updated_by: None, version: common_types::consts::API_VERSION, + tax_registration_id: encryptable_customer.tax_registration_id, }; Ok(Some( @@ -1371,12 +1373,18 @@ pub(super) fn get_customer_details_from_request( .and_then(|customer_details| customer_details.phone_country_code.clone()) .or(request.phone_country_code.clone()); + let tax_registration_id = request + .customer + .as_ref() + .and_then(|customer_details| customer_details.tax_registration_id.clone()); + CustomerDetails { customer_id, name: customer_name, email: customer_email, phone: customer_phone, phone_country_code: customer_phone_code, + tax_registration_id, } } diff --git a/crates/router/src/core/refunds_v2.rs b/crates/router/src/core/refunds_v2.rs index 4c39be723c5..ceab569885d 100644 --- a/crates/router/src/core/refunds_v2.rs +++ b/crates/router/src/core/refunds_v2.rs @@ -181,8 +181,13 @@ pub async fn trigger_refund_to_gateway( &payments::CallConnectorAction::Trigger, ); - let connector_response = - call_connector_service(state, &connector, add_access_token_result, router_data).await; + let connector_response = Box::pin(call_connector_service( + state, + &connector, + add_access_token_result, + router_data, + )) + .await; let refund_update = get_refund_update_object( state, @@ -276,8 +281,13 @@ pub async fn internal_trigger_refund_to_gateway( &payments::CallConnectorAction::Trigger, ); - let connector_response = - call_connector_service(state, &connector, add_access_token_result, router_data).await; + let connector_response = Box::pin(call_connector_service( + state, + &connector, + add_access_token_result, + router_data, + )) + .await; let refund_update = get_refund_update_object( state, @@ -807,10 +817,14 @@ pub async fn sync_refund_with_gateway( &payments::CallConnectorAction::Trigger, ); - let connector_response = - call_connector_service(state, &connector, add_access_token_result, router_data) - .await - .to_refund_failed_response()?; + let connector_response = Box::pin(call_connector_service( + state, + &connector, + add_access_token_result, + router_data, + )) + .await + .to_refund_failed_response()?; let connector_response = perform_integrity_check(connector_response); @@ -882,10 +896,14 @@ pub async fn internal_sync_refund_with_gateway( &payments::CallConnectorAction::Trigger, ); - let connector_response = - call_connector_service(state, &connector, add_access_token_result, router_data) - .await - .to_refund_failed_response()?; + let connector_response = Box::pin(call_connector_service( + state, + &connector, + add_access_token_result, + router_data, + )) + .await + .to_refund_failed_response()?; let connector_response = perform_integrity_check(connector_response); diff --git a/crates/router/src/core/relay/utils.rs b/crates/router/src/core/relay/utils.rs index e46abcd9e27..f958bf578d9 100644 --- a/crates/router/src/core/relay/utils.rs +++ b/crates/router/src/core/relay/utils.rs @@ -145,6 +145,7 @@ pub async fn construct_relay_refund_router_data( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index 954e33f76b5..f10c70433b6 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -128,6 +128,7 @@ pub fn construct_uas_router_data( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }) } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index b8d68f8f362..9ebecccc1c1 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -113,6 +113,7 @@ pub async fn construct_payout_router_data<'a, F>( first_name: a.first_name.clone().map(Encryptable::into_inner), last_name: a.last_name.clone().map(Encryptable::into_inner), state: a.state.map(Encryptable::into_inner), + origin_zip: a.origin_zip.map(Encryptable::into_inner), }; api_models::payments::Address { @@ -206,6 +207,7 @@ pub async fn construct_payout_router_data<'a, F>( email: c.email.map(Email::from), phone: c.phone.map(Encryptable::into_inner), phone_country_code: c.phone_country_code, + tax_registration_id: c.tax_registration_id.map(Encryptable::into_inner), }), connector_transfer_method_id, }, @@ -237,6 +239,7 @@ pub async fn construct_payout_router_data<'a, F>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -405,6 +408,7 @@ pub async fn construct_refund_router_data<'a, F>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -609,6 +613,7 @@ pub async fn construct_refund_router_data<'a, F>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) @@ -1036,6 +1041,7 @@ pub async fn construct_accept_dispute_router_data<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } @@ -1137,6 +1143,7 @@ pub async fn construct_submit_evidence_router_data<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } @@ -1244,6 +1251,7 @@ pub async fn construct_upload_file_router_data<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } @@ -1370,6 +1378,7 @@ pub async fn construct_payments_dynamic_tax_calculation_router_data( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } @@ -1474,6 +1483,7 @@ pub async fn construct_defend_dispute_router_data<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } @@ -1570,6 +1580,7 @@ pub async fn construct_retrieve_file_router_data<'a>( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index 65c3349c1a5..dfca569ba26 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -135,6 +135,7 @@ pub async fn construct_webhook_router_data( psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, }; Ok(router_data) } diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index 048645dd46a..18b7f7f7aff 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -593,6 +593,7 @@ mod storage { payment_id: address_new.payment_id.clone(), updated_by: storage_scheme.to_string(), email: address_new.email.clone(), + origin_zip: address_new.origin_zip.clone(), }; let redis_entry = kv::TypedSql { diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index c7fe77e46a0..bfbcd906fb4 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -49,7 +49,7 @@ pub use hyperswitch_domain_models::{ router_data::{ AccessToken, AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData, ErrorResponse, GooglePayDecryptedData, - GooglePayPaymentMethodDetails, PaymentMethodBalance, PaymentMethodToken, + GooglePayPaymentMethodDetails, L2L3Data, PaymentMethodBalance, PaymentMethodToken, RecurringMandatePaymentData, RouterData, }, router_data_v2::{ @@ -1163,6 +1163,7 @@ impl ForeignFrom<(&RouterData, T2) psd2_sca_exemption_type: data.psd2_sca_exemption_type, raw_connector_response: data.raw_connector_response.clone(), is_payment_id_from_merchant: data.is_payment_id_from_merchant, + l2_l3_data: data.l2_l3_data.clone(), } } } @@ -1232,6 +1233,7 @@ impl connector_mandate_request_reference_id: None, raw_connector_response: None, is_payment_id_from_merchant: data.is_payment_id_from_merchant, + l2_l3_data: None, } } } diff --git a/crates/router/src/types/api/customers.rs b/crates/router/src/types/api/customers.rs index 5e60acf1609..149239d2e3a 100644 --- a/crates/router/src/types/api/customers.rs +++ b/crates/router/src/types/api/customers.rs @@ -39,6 +39,7 @@ impl ForeignFrom<(domain::Customer, Option)> for Custo metadata: cust.metadata, address, default_payment_method_id: cust.default_payment_method_id, + tax_registration_id: cust.tax_registration_id, } .into() } @@ -61,6 +62,7 @@ impl ForeignFrom for CustomerResponse { default_billing_address: None, default_shipping_address: None, default_payment_method_id: cust.default_payment_method_id, + tax_registration_id: cust.tax_registration_id, } .into() } diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 046635a1e67..6c43f14c4ee 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -131,6 +131,7 @@ impl VerifyConnectorData { psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, } } } diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs index 2d1e98ec1b8..01d6cdda619 100644 --- a/crates/router/src/types/domain/address.rs +++ b/crates/router/src/types/domain/address.rs @@ -47,6 +47,8 @@ pub struct Address { pub updated_by: String, #[encrypt] pub email: Option>>, + #[encrypt] + pub origin_zip: Option>>, } /// Based on the flow, appropriate address has to be used @@ -187,6 +189,7 @@ impl behaviour::Conversion for Address { email: self.email.map(Encryption::from), payment_id: None, customer_id: None, + origin_zip: self.origin_zip.map(Encryption::from), }) } @@ -211,6 +214,7 @@ impl behaviour::Conversion for Address { last_name: other.last_name, phone_number: other.phone_number, email: other.email, + origin_zip: other.origin_zip, }, )), identifier.clone(), @@ -250,6 +254,7 @@ impl behaviour::Conversion for Address { ); encryptable }), + origin_zip: encryptable_address.origin_zip, }) } @@ -275,6 +280,7 @@ impl behaviour::Conversion for Address { email: self.email.map(Encryption::from), customer_id: None, payment_id: None, + origin_zip: self.origin_zip.map(Encryption::from), }) } } @@ -295,6 +301,7 @@ pub enum AddressUpdate { country_code: Option, updated_by: String, email: crypto::OptionalEncryptableEmail, + origin_zip: crypto::OptionalEncryptableSecretString, }, } @@ -315,6 +322,7 @@ impl From for AddressUpdateInternal { country_code, updated_by, email, + origin_zip, } => Self { city, country, @@ -330,6 +338,7 @@ impl From for AddressUpdateInternal { modified_at: date_time::convert_to_pdt(OffsetDateTime::now_utc()), updated_by, email: email.map(Encryption::from), + origin_zip: origin_zip.map(Encryption::from), }, } } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 02056774729..adadb32eef0 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -526,6 +526,7 @@ impl From<&domain::Address> for hyperswitch_domain_models::address::Address { zip: address.zip.clone().map(Encryptable::into_inner), first_name: address.first_name.clone().map(Encryptable::into_inner), last_name: address.last_name.clone().map(Encryptable::into_inner), + origin_zip: address.origin_zip.clone().map(Encryptable::into_inner), }) }; @@ -572,6 +573,7 @@ impl ForeignFrom for api_types::Address { zip: address.zip.clone().map(Encryptable::into_inner), first_name: address.first_name.clone().map(Encryptable::into_inner), last_name: address.last_name.clone().map(Encryptable::into_inner), + origin_zip: address.origin_zip.clone().map(Encryptable::into_inner), }) }; @@ -1544,6 +1546,7 @@ impl From for payments::AddressDetails { state: addr.state.map(Encryptable::into_inner), first_name: addr.first_name.map(Encryptable::into_inner), last_name: addr.last_name.map(Encryptable::into_inner), + origin_zip: addr.origin_zip.map(Encryptable::into_inner), } } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index a733de87656..df9653b6272 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -763,6 +763,7 @@ impl CustomerAddress for api_models::customers::CustomerRequest { .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address_details.origin_zip.clone(), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -796,6 +797,7 @@ impl CustomerAddress for api_models::customers::CustomerRequest { ); encryptable }), + origin_zip: encryptable_address.origin_zip, }) } @@ -825,6 +827,7 @@ impl CustomerAddress for api_models::customers::CustomerRequest { .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address_details.origin_zip.clone(), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -862,6 +865,7 @@ impl CustomerAddress for api_models::customers::CustomerRequest { ); encryptable }), + origin_zip: encryptable_address.origin_zip, }; Ok(domain::CustomerAddress { @@ -899,6 +903,7 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address_details.origin_zip.clone(), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -931,6 +936,7 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { ); encryptable }), + origin_zip: encryptable_address.origin_zip, }) } @@ -960,6 +966,7 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { .email .as_ref() .map(|a| a.clone().expose().switch_strategy()), + origin_zip: address_details.origin_zip.clone(), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -996,6 +1003,7 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { ); encryptable }), + origin_zip: encryptable_address.origin_zip, }; Ok(domain::CustomerAddress { diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 93398290d33..7bdecec4abe 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -291,6 +291,11 @@ pub async fn generate_sample_data( is_iframe_redirection_enabled: None, is_payment_id_from_merchant: None, payment_channel: None, + order_date: None, + discount_amount: None, + duty_amount: None, + tax_status: None, + shipping_amount_tax: None, }; let (connector_transaction_id, processor_transaction_data) = ConnectorTransactionId::form_id_and_data(attempt_id.clone()); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 3b5c0d5c2ab..cb3581e1c87 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -136,6 +136,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, } } @@ -210,6 +211,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { psd2_sca_exemption_type: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, } } diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index beca1c2df74..9a85d501056 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -64,6 +64,7 @@ impl AdyenTest { line3: None, first_name: Some(Secret::new("John".to_string())), last_name: Some(Secret::new("Dough".to_string())), + origin_zip: None, }), phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), diff --git a/crates/router/tests/connectors/dlocal.rs b/crates/router/tests/connectors/dlocal.rs index 0e9df8a4541..733956f5a66 100644 --- a/crates/router/tests/connectors/dlocal.rs +++ b/crates/router/tests/connectors/dlocal.rs @@ -436,6 +436,7 @@ pub fn get_payment_info() -> PaymentInfo { state: None, first_name: None, last_name: None, + origin_zip: None, }), email: None, }), diff --git a/crates/router/tests/connectors/multisafepay.rs b/crates/router/tests/connectors/multisafepay.rs index 1a81b00116f..11d846eb3a1 100644 --- a/crates/router/tests/connectors/multisafepay.rs +++ b/crates/router/tests/connectors/multisafepay.rs @@ -51,6 +51,7 @@ fn get_default_payment_info() -> Option { zip: Some(Secret::new("1033SC".to_string())), country: Some(api_models::enums::CountryAlpha2::NL), state: Some(Secret::new("Amsterdam".to_string())), + origin_zip: None, }), phone: None, email: None, diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index b56e1ec83de..01a3fb35492 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -56,6 +56,7 @@ fn get_default_payment_info() -> Option { state: None, first_name: Some(Secret::new("John".to_string())), last_name: Some(Secret::new("Doe".to_string())), + origin_zip: None, }), phone: None, email: None, @@ -91,6 +92,13 @@ fn payment_method_details() -> Option { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -383,7 +391,7 @@ async fn should_fail_payment_for_incorrect_cvc() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: MinorUnit::new(100), + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -394,6 +402,13 @@ async fn should_fail_payment_for_incorrect_cvc() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -425,7 +440,7 @@ async fn should_fail_payment_for_invalid_exp_month() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: MinorUnit::new(100), + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -436,6 +451,13 @@ async fn should_fail_payment_for_invalid_exp_month() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -467,7 +489,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: MinorUnit::new(100), + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -478,6 +500,13 @@ async fn should_fail_payment_for_incorrect_expiry_year() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 5d6dce2c58c..94c5beee934 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -470,6 +470,7 @@ pub trait ConnectorActions: Connector { email: Email::from_str("john.doe@example").ok(), phone: Some(Secret::new("620874518".to_string())), phone_country_code: Some("+31".to_string()), + tax_registration_id: Some("1232343243".to_string().into()), }), vendor_details: None, priority: None, @@ -555,6 +556,7 @@ pub trait ConnectorActions: Connector { authentication_id: None, raw_connector_response: None, is_payment_id_from_merchant: None, + l2_l3_data: None, } } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index a13fe48d255..3823008b956 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -323,7 +323,7 @@ async fn should_fail_payment_for_incorrect_card_number() { ..utils::CCardType::default().0 }), order_details: Some(vec![OrderDetailsWithAmount { - product_name: "test".to_string(), + product_name: "iphone 13".to_string(), quantity: 1, amount: MinorUnit::new(1000), product_img_link: None, @@ -336,6 +336,13 @@ async fn should_fail_payment_for_incorrect_card_number() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -368,7 +375,7 @@ async fn should_fail_payment_for_incorrect_cvc() { ..utils::CCardType::default().0 }), order_details: Some(vec![OrderDetailsWithAmount { - product_name: "test".to_string(), + product_name: "iphone 13".to_string(), quantity: 1, amount: MinorUnit::new(1000), product_img_link: None, @@ -381,6 +388,13 @@ async fn should_fail_payment_for_incorrect_cvc() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -413,7 +427,7 @@ async fn should_fail_payment_for_invalid_exp_month() { ..utils::CCardType::default().0 }), order_details: Some(vec![OrderDetailsWithAmount { - product_name: "test".to_string(), + product_name: "iphone 13".to_string(), quantity: 1, amount: MinorUnit::new(1000), product_img_link: None, @@ -426,6 +440,13 @@ async fn should_fail_payment_for_invalid_exp_month() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -458,7 +479,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { ..utils::CCardType::default().0 }), order_details: Some(vec![OrderDetailsWithAmount { - product_name: "test".to_string(), + product_name: "iphone 13".to_string(), quantity: 1, amount: MinorUnit::new(1000), product_img_link: None, @@ -471,6 +492,13 @@ async fn should_fail_payment_for_incorrect_expiry_year() { product_tax_code: None, tax_rate: None, total_tax_amount: None, + description: None, + sku: None, + upc: None, + commodity_code: None, + unit_of_measure: None, + total_amount: None, + unit_discount_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), diff --git a/cypress-tests/cypress/fixtures/confirm-body.json b/cypress-tests/cypress/fixtures/confirm-body.json index 0eb4f53c195..2140cf62019 100644 --- a/cypress-tests/cypress/fixtures/confirm-body.json +++ b/cypress-tests/cypress/fixtures/confirm-body.json @@ -20,7 +20,8 @@ "zip": "10001", "line1": "123", "line2": "Main Street", - "line3": "Apt 4B" + "line3": "Apt 4B", + "origin_zip": "10001" } }, diff --git a/cypress-tests/cypress/fixtures/create-customer-body.json b/cypress-tests/cypress/fixtures/create-customer-body.json index 025e6bec35b..a2de45384bc 100644 --- a/cypress-tests/cypress/fixtures/create-customer-body.json +++ b/cypress-tests/cypress/fixtures/create-customer-body.json @@ -13,7 +13,8 @@ "state": "Karnataka", "zip": "560095", "first_name": "John", - "last_name": "Doe" + "last_name": "Doe", + "origin_zip": "560095" }, "metadata": { "udf1": "value1", diff --git a/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/down.sql b/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/down.sql new file mode 100644 index 00000000000..1da1fbe35f2 --- /dev/null +++ b/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/down.sql @@ -0,0 +1,8 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent +DROP COLUMN IF EXISTS tax_status, +DROP COLUMN IF EXISTS discount_amount, +DROP COLUMN IF EXISTS shipping_amount_tax, +DROP COLUMN IF EXISTS duty_amount, +DROP COLUMN IF EXISTS order_date; + diff --git a/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/up.sql b/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/up.sql new file mode 100644 index 00000000000..f0b5a6aac60 --- /dev/null +++ b/migrations/2025-07-31-181024_add_l2_l3_fields_to_payment_intent/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here + +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS tax_status VARCHAR , +ADD COLUMN IF NOT EXISTS discount_amount BIGINT, +ADD COLUMN IF NOT EXISTS shipping_amount_tax BIGINT, +ADD COLUMN IF NOT EXISTS duty_amount BIGINT, +ADD COLUMN IF NOT EXISTS order_date TIMESTAMP; diff --git a/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/down.sql b/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/down.sql new file mode 100644 index 00000000000..67e398f1b82 --- /dev/null +++ b/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/down.sql @@ -0,0 +1 @@ +ALTER TABLE customers DROP COLUMN IF EXISTS tax_registration_id; diff --git a/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/up.sql b/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/up.sql new file mode 100644 index 00000000000..69659304dc7 --- /dev/null +++ b/migrations/2025-07-31-193521_add_tax_registration_id_to_customer/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE customers +ADD COLUMN +IF NOT EXISTS tax_registration_id BYTEA DEFAULT NULL; diff --git a/migrations/2025-08-01-050109_add_origin_zip_to_address/down.sql b/migrations/2025-08-01-050109_add_origin_zip_to_address/down.sql new file mode 100644 index 00000000000..6c5f5a02a04 --- /dev/null +++ b/migrations/2025-08-01-050109_add_origin_zip_to_address/down.sql @@ -0,0 +1,3 @@ +-- Drop origin_zip column from address table +ALTER TABLE address +DROP COLUMN origin_zip; diff --git a/migrations/2025-08-01-050109_add_origin_zip_to_address/up.sql b/migrations/2025-08-01-050109_add_origin_zip_to_address/up.sql new file mode 100644 index 00000000000..da93593d554 --- /dev/null +++ b/migrations/2025-08-01-050109_add_origin_zip_to_address/up.sql @@ -0,0 +1,3 @@ +-- Add origin_zip column to address table +ALTER TABLE address +ADD COLUMN IF NOT EXISTS origin_zip BYTEA;