diff --git a/.typos.toml b/.typos.toml index 67f819c2c52c..983ead3f75d6 100644 --- a/.typos.toml +++ b/.typos.toml @@ -60,7 +60,6 @@ nin = "nin" # National identification number, a field used by PayU connector requestor = "requestor" #Used in external 3ds flows substituters = "substituters" # Present in `flake.nix` unsuccess = "unsuccess" # Used in cryptopay request -authetication = "authetication" #UAS pre-authentication URL [files] extend-exclude = [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb67d8929e3..cf6a551700f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,42 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.12.19.1 + +### Features + +- **core:** Added customer phone_number and email to session token response for click to pay ([#6863](https://github.com/juspay/hyperswitch/pull/6863)) ([`092c79e`](https://github.com/juspay/hyperswitch/commit/092c79ec40c6af47a5d6654129411300e42eac56)) +- **klarna:** Klarna Kustom Checkout Integration ([#6839](https://github.com/juspay/hyperswitch/pull/6839)) ([`c525c9f`](https://github.com/juspay/hyperswitch/commit/c525c9f4c9d23802989bc594a4acd26c7d7cd27d)) +- **payment_methods:** Add support to pass apple pay recurring details to obtain apple pay merchant token ([#6770](https://github.com/juspay/hyperswitch/pull/6770)) ([`6074249`](https://github.com/juspay/hyperswitch/commit/607424992af4196f5a3e01477f64d794b3594a47)) +- **payments:** [Payment links] Add config for changing button text for payment links ([#6860](https://github.com/juspay/hyperswitch/pull/6860)) ([`46aad50`](https://github.com/juspay/hyperswitch/commit/46aad503b04efe60c54bbf4d5d5122696d9b1157)) +- **users:** Handle email url for users in different tenancies ([#6809](https://github.com/juspay/hyperswitch/pull/6809)) ([`839e69d`](https://github.com/juspay/hyperswitch/commit/839e69df241cf0eb2495f0ad3fc19cf32632c741)) + +### Bug Fixes + +- **connector:** [UNIFIED_AUTHENTICATION_SERVICE] change url path to `pre_authentication_processing` in pre-auth flow ([#6885](https://github.com/juspay/hyperswitch/pull/6885)) ([`f219b74`](https://github.com/juspay/hyperswitch/commit/f219b74cb6a100e07084afe6d9242a88f7127971)) + +### Refactors + +- **users:** Move roles schema to global interface ([#6862](https://github.com/juspay/hyperswitch/pull/6862)) ([`2d8af88`](https://github.com/juspay/hyperswitch/commit/2d8af882046bbfe309c5dbb5be9bfbd43e0c3831)) + +**Full Changelog:** [`2024.12.19.0...2024.12.19.1`](https://github.com/juspay/hyperswitch/compare/2024.12.19.0...2024.12.19.1) + +- - - + +## 2024.12.19.0 + +### Refactors + +- **dynamic_routing:** Update the authentication for update config to include JWT type ([#6785](https://github.com/juspay/hyperswitch/pull/6785)) ([`db51ec4`](https://github.com/juspay/hyperswitch/commit/db51ec43bc629dc20ceaa2bb57ede888d2d2fc2c)) + +### Miscellaneous Tasks + +- **env:** Remove unified_authentication_service base_url from integ, sandbox and production toml ([#6865](https://github.com/juspay/hyperswitch/pull/6865)) ([`03c71ea`](https://github.com/juspay/hyperswitch/commit/03c71ea366041af060b385dc9d88d4b9eda4abea)) + +**Full Changelog:** [`2024.12.18.0...2024.12.19.0`](https://github.com/juspay/hyperswitch/compare/2024.12.18.0...2024.12.19.0) + +- - - + ## 2024.12.18.0 ### Features diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 58a81caeed3a..e1a745fd9b17 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -3288,12 +3288,169 @@ } ], "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true + } + } + }, + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] + }, + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" } } }, "ApplePayRedirectData": { "type": "object" }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, "ApplePaySessionResponse": { "oneOf": [ { @@ -6280,6 +6437,22 @@ }, "transaction_currency_code": { "$ref": "#/components/schemas/Currency" + }, + "phone_number": { + "type": "string", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "nullable": true } } }, @@ -8484,6 +8657,14 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, @@ -11854,70 +12035,6 @@ } } }, - "OrderDetails": { - "type": "object", - "required": [ - "product_name", - "quantity" - ], - "properties": { - "product_name": { - "type": "string", - "description": "Name of the product that is being purchased", - "example": "shirt", - "maxLength": 255 - }, - "quantity": { - "type": "integer", - "format": "int32", - "description": "The quantity of the product to be purchased", - "example": 1, - "minimum": 0 - }, - "requires_shipping": { - "type": "boolean", - "nullable": true - }, - "product_img_link": { - "type": "string", - "description": "The image URL of the product", - "nullable": true - }, - "product_id": { - "type": "string", - "description": "ID of the product that is being purchased", - "nullable": true - }, - "category": { - "type": "string", - "description": "Category of the product that is being purchased", - "nullable": true - }, - "sub_category": { - "type": "string", - "description": "Sub category of the product that is being purchased", - "nullable": true - }, - "brand": { - "type": "string", - "description": "Brand of the product that is being purchased", - "nullable": true - }, - "product_type": { - "allOf": [ - { - "$ref": "#/components/schemas/ProductType" - } - ], - "nullable": true - }, - "product_tax_code": { - "type": "string", - "description": "The tax code for the product", - "nullable": true - } - } - }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -11940,7 +12057,21 @@ "minimum": 0 }, "amount": { - "$ref": "#/components/schemas/MinorUnit" + "type": "integer", + "format": "int64", + "description": "the amount per quantity of product" + }, + "tax_rate": { + "type": "number", + "format": "double", + "description": "tax rate applicable to the product", + "nullable": true + }, + "total_tax_amount": { + "type": "integer", + "format": "int64", + "description": "total tax amount applicable to the product", + "nullable": true }, "requires_shipping": { "type": "boolean", @@ -12353,6 +12484,17 @@ } } }, + { + "type": "object", + "required": [ + "klarna_checkout" + ], + "properties": { + "klarna_checkout": { + "type": "object" + } + } + }, { "type": "object", "required": [ @@ -12486,6 +12628,13 @@ "description": "The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", "example": 6540 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "The payment attempt tax_amount.", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -12797,6 +12946,11 @@ "type": "boolean", "description": "Toggle for HyperSwitch branding visibility", "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -12882,6 +13036,11 @@ } ], "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -18411,6 +18570,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 33dd5d87a473..4956df4713cc 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -5883,12 +5883,169 @@ } ], "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true + } + } + }, + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] + }, + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" } } }, "ApplePayRedirectData": { "type": "object" }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, "ApplePaySessionResponse": { "oneOf": [ { @@ -8882,6 +9039,22 @@ }, "transaction_currency_code": { "$ref": "#/components/schemas/Currency" + }, + "phone_number": { + "type": "string", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "nullable": true } } }, @@ -10982,6 +11155,14 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, @@ -14732,70 +14913,6 @@ } } }, - "OrderDetails": { - "type": "object", - "required": [ - "product_name", - "quantity" - ], - "properties": { - "product_name": { - "type": "string", - "description": "Name of the product that is being purchased", - "example": "shirt", - "maxLength": 255 - }, - "quantity": { - "type": "integer", - "format": "int32", - "description": "The quantity of the product to be purchased", - "example": 1, - "minimum": 0 - }, - "requires_shipping": { - "type": "boolean", - "nullable": true - }, - "product_img_link": { - "type": "string", - "description": "The image URL of the product", - "nullable": true - }, - "product_id": { - "type": "string", - "description": "ID of the product that is being purchased", - "nullable": true - }, - "category": { - "type": "string", - "description": "Category of the product that is being purchased", - "nullable": true - }, - "sub_category": { - "type": "string", - "description": "Sub category of the product that is being purchased", - "nullable": true - }, - "brand": { - "type": "string", - "description": "Brand of the product that is being purchased", - "nullable": true - }, - "product_type": { - "allOf": [ - { - "$ref": "#/components/schemas/ProductType" - } - ], - "nullable": true - }, - "product_tax_code": { - "type": "string", - "description": "The tax code for the product", - "nullable": true - } - } - }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -14818,7 +14935,21 @@ "minimum": 0 }, "amount": { - "$ref": "#/components/schemas/MinorUnit" + "type": "integer", + "format": "int64", + "description": "the amount per quantity of product" + }, + "tax_rate": { + "type": "number", + "format": "double", + "description": "tax rate applicable to the product", + "nullable": true + }, + "total_tax_amount": { + "type": "integer", + "format": "int64", + "description": "total tax amount applicable to the product", + "nullable": true }, "requires_shipping": { "type": "boolean", @@ -15224,6 +15355,17 @@ } } }, + { + "type": "object", + "required": [ + "klarna_checkout" + ], + "properties": { + "klarna_checkout": { + "type": "object" + } + } + }, { "type": "object", "required": [ @@ -15357,6 +15499,13 @@ "description": "The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", "example": 6540 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "The payment attempt tax_amount.", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -15668,6 +15817,11 @@ "type": "boolean", "description": "Toggle for HyperSwitch branding visibility", "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -15753,6 +15907,11 @@ } ], "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -17155,6 +17314,13 @@ "nullable": true, "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -17533,6 +17699,13 @@ "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "$ref": "#/components/schemas/Currency" }, @@ -18659,6 +18832,13 @@ "nullable": true, "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -19805,6 +19985,13 @@ "nullable": true, "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -22570,6 +22757,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { diff --git a/config/config.example.toml b/config/config.example.toml index 5c55d57cf77b..4d9950226451 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -759,8 +759,15 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant +[multitenancy.tenants.public] +base_url = "http://localhost:8080" # URL of the tenant +schema = "public" # Postgres db schema +redis_key_prefix = "" # Redis key distinguisher +clickhouse_database = "default" # Clickhouse database + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" # Control center URL + [user_auth_methods] encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index c470f364fe74..967b847dae51 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -305,8 +305,14 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data. enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table diff --git a/config/development.toml b/config/development.toml index fd84d626d377..6250e431bd4f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -775,8 +775,14 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index dbd5286b2900..b861e47e5945 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -633,8 +633,14 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default" } -[multitenancy.tenants] -public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F" diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index d5e875672b88..22674f29a92f 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -2769,6 +2769,8 @@ pub struct PaymentLinkConfigRequest { /// Custom layout for details section #[schema(value_type = Option, example = "layout1")] pub details_layout: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] @@ -2838,6 +2840,8 @@ pub struct PaymentLinkConfig { pub details_layout: Option, /// Toggle for HyperSwitch branding visibility pub branding_visibility: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 0381e347f4f1..1c2a5c5a7169 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -741,6 +741,10 @@ pub struct PaymentsRequest { // Makes the field mandatory in PaymentsCreateRequest pub amount: Option, + /// Total tax amount applicable to the order + #[schema(value_type = Option, example = 6540)] + pub order_tax_amount: Option, + /// The three letter ISO currency code in uppercase. Eg: 'USD' to charge US Dollars #[schema(example = "USD", value_type = Option)] #[mandatory_in(PaymentsCreateRequest = Currency)] @@ -1308,6 +1312,9 @@ pub struct PaymentAttemptResponse { /// The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., #[schema(value_type = i64, example = 6540)] pub amount: MinorUnit, + /// The payment attempt tax_amount. + #[schema(value_type = Option, example = 6540)] + pub order_tax_amount: Option, /// The currency of the amount of the payment attempt #[schema(value_type = Option, example = "USD")] pub currency: Option, @@ -1858,6 +1865,7 @@ pub enum PayLaterData { /// The token for the sdk workflow token: String, }, + KlarnaCheckout {}, /// For Affirm redirect as PayLater Option AffirmRedirect {}, /// For AfterpayClearpay redirect as PayLater Option @@ -1915,6 +1923,7 @@ impl GetAddressFromPaymentMethodData for PayLaterData { | Self::WalleyRedirect {} | Self::AlmaRedirect {} | Self::KlarnaSdk { .. } + | Self::KlarnaCheckout {} | Self::AffirmRedirect {} | Self::AtomeRedirect {} => None, } @@ -2376,6 +2385,7 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, + Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, @@ -5471,7 +5481,7 @@ pub struct PaymentsRetrieveRequest { pub expand_attempts: Option, } -#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[derive(Debug, Default, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct OrderDetailsWithAmount { /// Name of the product that is being purchased #[schema(max_length = 255, example = "shirt")] @@ -5480,7 +5490,13 @@ pub struct OrderDetailsWithAmount { #[schema(example = 1)] pub quantity: u16, /// the amount per quantity of product + #[schema(value_type = i64)] pub amount: MinorUnit, + /// tax rate applicable to the product + pub tax_rate: Option, + /// total tax amount applicable to the product + #[schema(value_type = Option)] + pub total_tax_amount: Option, // Does the order includes shipping pub requires_shipping: Option, /// The image URL of the product @@ -5501,32 +5517,6 @@ pub struct OrderDetailsWithAmount { impl masking::SerializableSecret for OrderDetailsWithAmount {} -#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] -pub struct OrderDetails { - /// Name of the product that is being purchased - #[schema(max_length = 255, example = "shirt")] - pub product_name: String, - /// The quantity of the product to be purchased - #[schema(example = 1)] - pub quantity: u16, - // Does the order include shipping - pub requires_shipping: Option, - /// The image URL of the product - pub product_img_link: Option, - /// ID of the product that is being purchased - pub product_id: Option, - /// Category of the product that is being purchased - pub category: Option, - /// Sub category of the product that is being purchased - pub sub_category: Option, - /// Brand of the product that is being purchased - pub brand: Option, - /// Type of the product that is being purchased - pub product_type: Option, - /// The tax code for the product - pub product_tax_code: Option, -} - #[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct RedirectResponse { #[schema(value_type = Option)] @@ -6237,6 +6227,57 @@ pub struct ApplePayPaymentRequest { #[serde(skip_serializing_if = "Option::is_none")] /// The required shipping contacht fields for connector pub required_shipping_contact_fields: Option, + /// Recurring payment request for apple pay Merchant Token + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_request: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringPaymentRequest { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingRequest, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingRequest { + /// The amount of the recurring payment + #[schema(value_type = String, example = "38.02")] + pub amount: StringMajorUnit, + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The time that the payment occurs as part of a successful transaction + pub payment_timing: ApplePayPaymentTiming, + /// The date of the first payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ApplePayPaymentTiming { + /// A value that specifies that the payment occurs when the transaction is complete + Immediate, + /// A value that specifies that the payment occurs on a regular basis + Recurring, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] @@ -6514,6 +6555,49 @@ pub struct FeatureMetadata { /// Additional tags to be used for global search #[schema(value_type = Option>)] pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, } ///frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None @@ -6863,6 +6947,7 @@ pub struct PaymentLinkDetails { pub background_image: Option, pub details_layout: Option, pub branding_visibility: Option, + pub payment_button_text: Option, } #[derive(Debug, serde::Serialize, Clone)] @@ -6872,6 +6957,7 @@ pub struct SecurePaymentLinkDetails { pub show_card_form_by_default: bool, #[serde(flatten)] pub payment_link_details: PaymentLinkDetails, + pub payment_button_text: Option, } #[derive(Debug, serde::Serialize)] @@ -7007,6 +7093,11 @@ pub struct ClickToPaySessionResponse { pub transaction_amount: StringMajorUnit, #[schema(value_type = Currency)] pub transaction_currency_code: common_enums::Currency, + #[schema(value_type = Option, max_length = 255, example = "9123456789")] + pub phone_number: Option>, + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + pub email: Option, + pub phone_country_code: Option, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index fc78ffc8c718..2d2f3f576940 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -571,6 +571,7 @@ pub struct PaymentLinkConfigRequest { pub show_card_form_by_default: Option, pub background_image: Option, pub details_layout: Option, + pub payment_button_text: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 892e8867ab07..57549ed201f2 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -167,6 +167,8 @@ pub struct PaymentLinkConfigRequestForPayments { pub background_image: Option, /// Custom layout for details section pub details_layout: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } common_utils::impl_to_sql_from_sql_json!(PaymentLinkConfigRequestForPayments); diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 94f1fa8d2664..7806a7c6e1b8 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -30,6 +30,10 @@ pub struct OrderDetailsWithAmount { pub product_type: Option, /// The tax code for the product pub product_tax_code: Option, + /// tax rate applicable to the product + pub tax_rate: Option, + /// total tax amount applicable to the product + pub total_tax_amount: Option, } impl masking::SerializableSecret for OrderDetailsWithAmount {} @@ -44,8 +48,55 @@ pub struct FeatureMetadata { // TODO: Convert this to hashedstrings to avoid PII sensitive data /// Additional tags to be used for global search pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, } -impl masking::SerializableSecret for FeatureMetadata {} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, +} + +common_utils::impl_to_sql_from_sql_json!(ApplePayRecurringDetails); +common_utils::impl_to_sql_from_sql_json!(ApplePayRegularBillingDetails); +common_utils::impl_to_sql_from_sql_json!(RecurringPaymentIntervalUnit); + common_utils::impl_to_sql_from_sql_json!(FeatureMetadata); #[derive(Default, Debug, Eq, PartialEq, Deserialize, Serialize, Clone)] diff --git a/crates/external_services/src/email.rs b/crates/external_services/src/email.rs index 2e05a26e1f1c..a98b03c0b545 100644 --- a/crates/external_services/src/email.rs +++ b/crates/external_services/src/email.rs @@ -47,6 +47,7 @@ pub trait EmailService: Sync + Send + dyn_clone::DynClone { /// Compose and send email using the email data async fn compose_and_send_email( &self, + base_url: &str, email_data: Box, proxy_url: Option<&String>, ) -> EmailResult<()>; @@ -60,10 +61,11 @@ where { async fn compose_and_send_email( &self, + base_url: &str, email_data: Box, proxy_url: Option<&String>, ) -> EmailResult<()> { - let email_data = email_data.get_email_data(); + let email_data = email_data.get_email_data(base_url); let email_data = email_data.await?; let EmailContents { @@ -113,7 +115,7 @@ pub struct EmailContents { #[async_trait::async_trait] pub trait EmailData { /// Get the email contents - async fn get_email_data(&self) -> CustomResult; + async fn get_email_data(&self, base_url: &str) -> CustomResult; } dyn_clone::clone_trait_object!(EmailClient); diff --git a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs index 2e4503c34691..7970b1b1d42e 100644 --- a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs @@ -555,6 +555,7 @@ impl merchant_identifier: Some(session_token_data.merchant_identifier), required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }), connector: "bluesnap".to_string(), delayed_session_token: false, diff --git a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index 479abf13b13a..a60e8bdc79ed 100644 --- a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -746,6 +746,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> email: Some(match paylater { PayLaterData::KlarnaRedirect {} => item.router_data.get_billing_email()?, PayLaterData::KlarnaSdk { token: _ } + | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect {} | PayLaterData::AfterpayClearpayRedirect {} | PayLaterData::PayBrightRedirect {} diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index 08ca9a54fa41..5abc93b3f882 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -85,6 +85,7 @@ impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequ PayLaterData::AfterpayClearpayRedirect { .. } | PayLaterData::KlarnaRedirect { .. } | PayLaterData::KlarnaSdk { .. } + | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect { .. } | PayLaterData::PayBrightRedirect { .. } | PayLaterData::WalleyRedirect { .. } diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index 7a30a434a5d7..41dc60f8248e 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -751,6 +751,7 @@ impl TryFrom<&PayLaterData> for ZenPaymentsRequest { match value { PayLaterData::KlarnaRedirect { .. } | PayLaterData::KlarnaSdk { .. } + | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect {} | PayLaterData::AfterpayClearpayRedirect { .. } | PayLaterData::PayBrightRedirect {} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 6dce48e69965..cf32d966e056 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2092,6 +2092,7 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, + KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -2216,6 +2217,7 @@ impl From for PaymentMethodDataType { PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { payment_method_data::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, payment_method_data::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, + payment_method_data::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout, payment_method_data::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, payment_method_data::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 0e8722644bd4..fe152e876ccf 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -32,10 +32,16 @@ pub trait PayoutAttemptInterface {} pub trait PayoutsInterface {} use api_models::payments::{ + ApplePayRecurringDetails as ApiApplePayRecurringDetails, + ApplePayRegularBillingDetails as ApiApplePayRegularBillingDetails, FeatureMetadata as ApiFeatureMetadata, OrderDetailsWithAmount as ApiOrderDetailsWithAmount, + RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, RedirectResponse as ApiRedirectResponse, }; -use diesel_models::types::{FeatureMetadata, OrderDetailsWithAmount, RedirectResponse}; +use diesel_models::types::{ + ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, + OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, +}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { @@ -75,10 +81,13 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { let ApiFeatureMetadata { redirect_response, search_tags, + apple_pay_recurring_details, } = from; Self { redirect_response: redirect_response.map(RedirectResponse::convert_from), search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(ApplePayRecurringDetails::convert_from), } } @@ -86,11 +95,14 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { let Self { redirect_response, search_tags, + apple_pay_recurring_details, } = self; ApiFeatureMetadata { redirect_response: redirect_response .map(|redirect_response| redirect_response.convert_back()), search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(|value| value.convert_back()), } } } @@ -119,6 +131,77 @@ impl ApiModelToDieselModelConvertor for RedirectResponse { } } +impl ApiModelToDieselModelConvertor + for RecurringPaymentIntervalUnit +{ + fn convert_from(from: ApiRecurringPaymentIntervalUnit) -> Self { + match from { + ApiRecurringPaymentIntervalUnit::Year => Self::Year, + ApiRecurringPaymentIntervalUnit::Month => Self::Month, + ApiRecurringPaymentIntervalUnit::Day => Self::Day, + ApiRecurringPaymentIntervalUnit::Hour => Self::Hour, + ApiRecurringPaymentIntervalUnit::Minute => Self::Minute, + } + } + fn convert_back(self) -> ApiRecurringPaymentIntervalUnit { + match self { + Self::Year => ApiRecurringPaymentIntervalUnit::Year, + Self::Month => ApiRecurringPaymentIntervalUnit::Month, + Self::Day => ApiRecurringPaymentIntervalUnit::Day, + Self::Hour => ApiRecurringPaymentIntervalUnit::Hour, + Self::Minute => ApiRecurringPaymentIntervalUnit::Minute, + } + } +} + +impl ApiModelToDieselModelConvertor + for ApplePayRegularBillingDetails +{ + fn convert_from(from: ApiApplePayRegularBillingDetails) -> Self { + Self { + label: from.label, + recurring_payment_start_date: from.recurring_payment_start_date, + recurring_payment_end_date: from.recurring_payment_end_date, + recurring_payment_interval_unit: from + .recurring_payment_interval_unit + .map(RecurringPaymentIntervalUnit::convert_from), + recurring_payment_interval_count: from.recurring_payment_interval_count, + } + } + + fn convert_back(self) -> ApiApplePayRegularBillingDetails { + ApiApplePayRegularBillingDetails { + label: self.label, + recurring_payment_start_date: self.recurring_payment_start_date, + recurring_payment_end_date: self.recurring_payment_end_date, + recurring_payment_interval_unit: self + .recurring_payment_interval_unit + .map(|value| value.convert_back()), + recurring_payment_interval_count: self.recurring_payment_interval_count, + } + } +} + +impl ApiModelToDieselModelConvertor for ApplePayRecurringDetails { + fn convert_from(from: ApiApplePayRecurringDetails) -> Self { + Self { + payment_description: from.payment_description, + regular_billing: ApplePayRegularBillingDetails::convert_from(from.regular_billing), + billing_agreement: from.billing_agreement, + management_url: from.management_url, + } + } + + fn convert_back(self) -> ApiApplePayRecurringDetails { + ApiApplePayRecurringDetails { + payment_description: self.payment_description, + regular_billing: self.regular_billing.convert_back(), + billing_agreement: self.billing_agreement, + management_url: self.management_url, + } + } +} + impl ApiModelToDieselModelConvertor for OrderDetailsWithAmount { fn convert_from(from: ApiOrderDetailsWithAmount) -> Self { let ApiOrderDetailsWithAmount { @@ -133,6 +216,8 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW brand, product_type, product_tax_code, + tax_rate, + total_tax_amount, } = from; Self { product_name, @@ -146,6 +231,8 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW brand, product_type, product_tax_code, + tax_rate, + total_tax_amount, } } @@ -162,6 +249,8 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW brand, product_type, product_tax_code, + tax_rate, + total_tax_amount, } = self; ApiOrderDetailsWithAmount { product_name, @@ -175,6 +264,8 @@ impl ApiModelToDieselModelConvertor for OrderDetailsW brand, product_type, product_tax_code, + tax_rate, + total_tax_amount, } } } @@ -209,6 +300,7 @@ impl ApiModelToDieselModelConvertor background_image, ) }), + payment_button_text: item.payment_button_text, } } fn convert_back(self) -> api_models::admin::PaymentLinkConfigRequest { @@ -224,6 +316,7 @@ impl ApiModelToDieselModelConvertor transaction_details, background_image, details_layout, + payment_button_text, } = self; api_models::admin::PaymentLinkConfigRequest { theme, @@ -243,6 +336,7 @@ impl ApiModelToDieselModelConvertor }), background_image: background_image .map(|background_image| background_image.convert_back()), + payment_button_text, } } } diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 169719cb5dad..8c19a20ef322 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -155,6 +155,7 @@ pub enum CardRedirectData { pub enum PayLaterData { KlarnaRedirect {}, KlarnaSdk { token: String }, + KlarnaCheckout {}, AffirmRedirect {}, AfterpayClearpayRedirect {}, PayBrightRedirect {}, @@ -897,6 +898,7 @@ impl From for PayLaterData { match value { api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, + api_models::payments::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout {}, api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect {} @@ -1550,6 +1552,7 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, + Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 2f68b481d881..55624af9f728 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -29,6 +29,7 @@ pub struct PaymentsAuthorizeData { /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount /// ``` pub amount: i64, + pub order_tax_amount: Option, pub email: Option, pub customer_name: Option>, pub currency: storage_enums::Currency, @@ -830,6 +831,7 @@ pub struct PaymentsSessionData { pub email: Option, // Minor Unit amount for amount frame work pub minor_amount: MinorUnit, + pub apple_pay_recurring_details: Option, } #[derive(Debug, Clone, Default)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 65c76f8929f2..a55f6ed4214c 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -381,7 +381,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SwishQrData, api_models::payments::AirwallexData, api_models::payments::NoonData, - api_models::payments::OrderDetails, api_models::payments::OrderDetailsWithAmount, api_models::payments::NextActionType, api_models::payments::WalletData, @@ -419,6 +418,12 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayPaymentRequest, api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::ApplePayAddressParameters, api_models::payments::AmountInfo, api_models::payments::ClickToPaySessionResponse, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index b5de979b3aef..c7425aed2a51 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -341,7 +341,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SwishQrData, api_models::payments::AirwallexData, api_models::payments::NoonData, - api_models::payments::OrderDetails, api_models::payments::OrderDetailsWithAmount, api_models::payments::NextActionType, api_models::payments::WalletData, @@ -381,6 +380,12 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, api_models::payments::ApplePayAddressParameters, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::AmountInfo, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 4ab2a8fd5c77..ccd43d25b8ee 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -169,6 +169,12 @@ pub struct Tenant { pub schema: String, pub redis_key_prefix: String, pub clickhouse_database: String, + pub user: TenantUserConfig, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct TenantUserConfig { + pub control_center_url: String, } impl storage_impl::config::TenantConfig for Tenant { @@ -1130,6 +1136,7 @@ impl<'de> Deserialize<'de> for TenantConfig { schema: String, redis_key_prefix: String, clickhouse_database: String, + user: TenantUserConfig, } let hashmap = >::deserialize(deserializer)?; @@ -1146,6 +1153,7 @@ impl<'de> Deserialize<'de> for TenantConfig { schema: value.schema, redis_key_prefix: value.redis_key_prefix, clickhouse_database: value.clickhouse_database, + user: value.user, }, ) }) diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index d0dad91f7170..aece1df65463 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2350,7 +2350,8 @@ impl check_required_field(billing_address, "billing")?; Ok(AdyenPaymentMethod::Atome) } - domain::payments::PayLaterData::KlarnaSdk { .. } => { + domain::payments::PayLaterData::KlarnaCheckout {} + | domain::payments::PayLaterData::KlarnaSdk { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Adyen"), ) diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index d2bdaae0493d..77e2a027d835 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -448,7 +448,24 @@ impl let endpoint = build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?; - Ok(format!("{endpoint}ordermanagement/v1/orders/{order_id}")) + let payment_experience = req.request.payment_experience; + + match payment_experience { + Some(common_enums::PaymentExperience::InvokeSdkClient) => { + Ok(format!("{endpoint}ordermanagement/v1/orders/{order_id}")) + } + Some(common_enums::PaymentExperience::RedirectToUrl) => { + Ok(format!("{endpoint}checkout/v3/orders/{order_id}")) + } + None => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: "payment_experience not supported".to_string(), + connector: "klarna", + })), + _ => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: "payment_experience not supported".to_string(), + connector: "klarna", + })), + } } fn build_request( @@ -653,6 +670,122 @@ impl })), } } + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + match (payment_experience, payment_method_type) { + ( + common_enums::PaymentExperience::RedirectToUrl, + common_enums::PaymentMethodType::Klarna, + ) => Ok(format!("{endpoint}checkout/v3/orders",)), + ( + common_enums::PaymentExperience::DisplayQrCode + | common_enums::PaymentExperience::DisplayWaitScreen + | common_enums::PaymentExperience::InvokePaymentApp + | common_enums::PaymentExperience::InvokeSdkClient + | common_enums::PaymentExperience::LinkWallet + | common_enums::PaymentExperience::OneClick + | common_enums::PaymentExperience::RedirectToUrl + | common_enums::PaymentExperience::CollectOtp, + common_enums::PaymentMethodType::Ach + | common_enums::PaymentMethodType::Affirm + | common_enums::PaymentMethodType::AfterpayClearpay + | common_enums::PaymentMethodType::Alfamart + | common_enums::PaymentMethodType::AliPay + | common_enums::PaymentMethodType::AliPayHk + | common_enums::PaymentMethodType::Alma + | common_enums::PaymentMethodType::ApplePay + | common_enums::PaymentMethodType::Atome + | common_enums::PaymentMethodType::Bacs + | common_enums::PaymentMethodType::BancontactCard + | common_enums::PaymentMethodType::Becs + | common_enums::PaymentMethodType::Benefit + | common_enums::PaymentMethodType::Bizum + | common_enums::PaymentMethodType::Blik + | common_enums::PaymentMethodType::Boleto + | common_enums::PaymentMethodType::BcaBankTransfer + | common_enums::PaymentMethodType::BniVa + | common_enums::PaymentMethodType::BriVa + | common_enums::PaymentMethodType::CardRedirect + | common_enums::PaymentMethodType::CimbVa + | common_enums::PaymentMethodType::ClassicReward + | common_enums::PaymentMethodType::Credit + | common_enums::PaymentMethodType::CryptoCurrency + | common_enums::PaymentMethodType::Cashapp + | common_enums::PaymentMethodType::Dana + | common_enums::PaymentMethodType::DanamonVa + | common_enums::PaymentMethodType::Debit + | common_enums::PaymentMethodType::DirectCarrierBilling + | common_enums::PaymentMethodType::Efecty + | common_enums::PaymentMethodType::Eps + | common_enums::PaymentMethodType::Evoucher + | common_enums::PaymentMethodType::Giropay + | common_enums::PaymentMethodType::Givex + | common_enums::PaymentMethodType::GooglePay + | common_enums::PaymentMethodType::GoPay + | common_enums::PaymentMethodType::Gcash + | common_enums::PaymentMethodType::Ideal + | common_enums::PaymentMethodType::Interac + | common_enums::PaymentMethodType::Indomaret + | common_enums::PaymentMethodType::Klarna + | common_enums::PaymentMethodType::KakaoPay + | common_enums::PaymentMethodType::MandiriVa + | common_enums::PaymentMethodType::Knet + | common_enums::PaymentMethodType::MbWay + | common_enums::PaymentMethodType::MobilePay + | common_enums::PaymentMethodType::Momo + | common_enums::PaymentMethodType::MomoAtm + | common_enums::PaymentMethodType::Multibanco + | common_enums::PaymentMethodType::LocalBankRedirect + | common_enums::PaymentMethodType::OnlineBankingThailand + | common_enums::PaymentMethodType::OnlineBankingCzechRepublic + | common_enums::PaymentMethodType::OnlineBankingFinland + | common_enums::PaymentMethodType::OnlineBankingFpx + | common_enums::PaymentMethodType::OnlineBankingPoland + | common_enums::PaymentMethodType::OnlineBankingSlovakia + | common_enums::PaymentMethodType::Oxxo + | common_enums::PaymentMethodType::PagoEfectivo + | common_enums::PaymentMethodType::PermataBankTransfer + | common_enums::PaymentMethodType::OpenBankingUk + | common_enums::PaymentMethodType::PayBright + | common_enums::PaymentMethodType::Paypal + | common_enums::PaymentMethodType::Paze + | common_enums::PaymentMethodType::Pix + | common_enums::PaymentMethodType::PaySafeCard + | common_enums::PaymentMethodType::Przelewy24 + | common_enums::PaymentMethodType::Pse + | common_enums::PaymentMethodType::RedCompra + | common_enums::PaymentMethodType::RedPagos + | common_enums::PaymentMethodType::SamsungPay + | common_enums::PaymentMethodType::Sepa + | common_enums::PaymentMethodType::Sofort + | common_enums::PaymentMethodType::Swish + | common_enums::PaymentMethodType::TouchNGo + | common_enums::PaymentMethodType::Trustly + | common_enums::PaymentMethodType::Twint + | common_enums::PaymentMethodType::UpiCollect + | common_enums::PaymentMethodType::UpiIntent + | common_enums::PaymentMethodType::Venmo + | common_enums::PaymentMethodType::Vipps + | common_enums::PaymentMethodType::Walley + | common_enums::PaymentMethodType::WeChatPay + | common_enums::PaymentMethodType::SevenEleven + | common_enums::PaymentMethodType::Lawson + | common_enums::PaymentMethodType::LocalBankTransfer + | common_enums::PaymentMethodType::MiniStop + | common_enums::PaymentMethodType::FamilyMart + | common_enums::PaymentMethodType::Seicomart + | common_enums::PaymentMethodType::PayEasy + | common_enums::PaymentMethodType::Mifinity + | common_enums::PaymentMethodType::Fps + | common_enums::PaymentMethodType::DuitNow + | common_enums::PaymentMethodType::PromptPay + | common_enums::PaymentMethodType::VietQr + | common_enums::PaymentMethodType::OpenBankingPIS, + ) => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: payment_method_type.to_string(), + connector: "klarna", + })), + } + } domain::PaymentMethodData::Card(_) | domain::PaymentMethodData::CardRedirect(_) @@ -726,7 +859,7 @@ impl event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: klarna::KlarnaPaymentsResponse = res + let response: klarna::KlarnaAuthResponse = res .response .parse_struct("KlarnaPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index 803dc4a428d2..0826b0f2665b 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -1,7 +1,9 @@ use api_models::payments; use common_utils::{pii, types::MinorUnit}; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::router_data::KlarnaSdkResponse; +use hyperswitch_domain_models::{ + router_data::KlarnaSdkResponse, router_response_types::RedirectForm, +}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -10,7 +12,7 @@ use crate::{ self, AddressData, AddressDetailsData, PaymentsAuthorizeRequestData, RouterData, }, core::errors, - types::{self, api, storage::enums, transformers::ForeignFrom}, + types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] @@ -61,9 +63,29 @@ impl TryFrom<&Option> for KlarnaConnectorMetadataObject { } } -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PaymentMethodSpecifics { + KlarnaCheckout(KlarnaCheckoutRequestData), + KlarnaSdk, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct MerchantURLs { + terms: String, + checkout: String, + confirmation: String, + push: String, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct KlarnaCheckoutRequestData { + merchant_urls: MerchantURLs, + options: CheckoutOptions, +} + +#[derive(Default, Debug, Deserialize, Serialize)] pub struct KlarnaPaymentsRequest { - auto_capture: bool, order_lines: Vec, order_amount: MinorUnit, purchase_country: enums::CountryAlpha2, @@ -71,15 +93,32 @@ pub struct KlarnaPaymentsRequest { merchant_reference1: Option, merchant_reference2: Option, shipping_address: Option, + auto_capture: Option, + order_tax_amount: Option, + #[serde(flatten)] + payment_method_specifics: Option, } #[derive(Debug, Deserialize, Serialize)] -pub struct KlarnaPaymentsResponse { +#[serde(untagged)] +pub enum KlarnaAuthResponse { + KlarnaPaymentsAuthResponse(PaymentsResponse), + KlarnaCheckoutAuthResponse(CheckoutResponse), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PaymentsResponse { order_id: String, fraud_status: KlarnaFraudStatus, authorized_payment_method: Option, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CheckoutResponse { + order_id: String, + status: KlarnaCheckoutStatus, + html_snippet: String, +} #[derive(Debug, Clone, Deserialize, Serialize)] pub struct AuthorizedPaymentMethod { #[serde(rename = "type")] @@ -106,7 +145,7 @@ pub struct KlarnaSessionRequest { shipping_address: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct KlarnaShippingAddress { city: String, country: enums::CountryAlpha2, @@ -120,6 +159,10 @@ pub struct KlarnaShippingAddress { street_address2: Option>, } +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct CheckoutOptions { + auto_capture: bool, +} #[derive(Deserialize, Serialize, Debug)] pub struct KlarnaSessionResponse { pub client_token: Secret, @@ -149,6 +192,8 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsSessionRouterData>> for KlarnaSes quantity: data.quantity, unit_price: data.amount, total_amount: data.amount * data.quantity, + total_tax_amount: None, + tax_rate: None, }) .collect(), shipping_address: get_address_info(item.router_data.get_optional_shipping()) @@ -190,29 +235,100 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP item: &KlarnaRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let request = &item.router_data.request; - match request.order_details.clone() { - Some(order_details) => Ok(Self { - purchase_country: item.router_data.get_billing_country()?, - purchase_currency: request.currency, - order_amount: item.amount, - order_lines: order_details - .iter() - .map(|data| OrderLines { - name: data.product_name.clone(), - quantity: data.quantity, - unit_price: data.amount, - total_amount: data.amount * data.quantity, - }) - .collect(), - merchant_reference1: Some(item.router_data.connector_request_reference_id.clone()), - merchant_reference2: item.router_data.request.merchant_order_reference_id.clone(), - auto_capture: request.is_auto_capture()?, - shipping_address: get_address_info(item.router_data.get_optional_shipping()) - .transpose()?, - }), - None => Err(report!(errors::ConnectorError::MissingRequiredField { - field_name: "order_details" - })), + let payment_method_data = request.payment_method_data.clone(); + let return_url = item.router_data.request.get_return_url()?; + let webhook_url = item.router_data.request.get_webhook_url()?; + match payment_method_data { + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaSdk { .. }) => { + match request.order_details.clone() { + Some(order_details) => Ok(Self { + purchase_country: item.router_data.get_billing_country()?, + purchase_currency: request.currency, + order_amount: item.amount, + order_lines: order_details + .iter() + .map(|data| OrderLines { + name: data.product_name.clone(), + quantity: data.quantity, + unit_price: data.amount, + total_amount: data.amount * data.quantity, + total_tax_amount: None, + tax_rate: None, + }) + .collect(), + merchant_reference1: Some( + item.router_data.connector_request_reference_id.clone(), + ), + merchant_reference2: item + .router_data + .request + .merchant_order_reference_id + .clone(), + auto_capture: Some(request.is_auto_capture()?), + shipping_address: get_address_info( + item.router_data.get_optional_shipping(), + ) + .transpose()?, + order_tax_amount: None, + payment_method_specifics: None, + }), + None => Err(report!(errors::ConnectorError::MissingRequiredField { + field_name: "order_details" + })), + } + } + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + match request.order_details.clone() { + Some(order_details) => Ok(Self { + purchase_country: item.router_data.get_billing_country()?, + purchase_currency: request.currency, + order_amount: item.amount + - request.order_tax_amount.unwrap_or(MinorUnit::zero()), + order_tax_amount: request.order_tax_amount, + order_lines: order_details + .iter() + .map(|data| OrderLines { + name: data.product_name.clone(), + quantity: data.quantity, + unit_price: data.amount, + total_amount: data.amount * data.quantity, + total_tax_amount: data.total_tax_amount, + tax_rate: data.tax_rate, + }) + .collect(), + payment_method_specifics: Some(PaymentMethodSpecifics::KlarnaCheckout( + KlarnaCheckoutRequestData { + merchant_urls: MerchantURLs { + terms: return_url.clone(), + checkout: return_url.clone(), + confirmation: return_url, + push: webhook_url, + }, + options: CheckoutOptions { + auto_capture: request.is_auto_capture()?, + }, + }, + )), + shipping_address: get_address_info( + item.router_data.get_optional_shipping(), + ) + .transpose()?, + merchant_reference1: Some( + item.router_data.connector_request_reference_id.clone(), + ), + merchant_reference2: item + .router_data + .request + .merchant_order_reference_id + .clone(), + auto_capture: None, + }), + None => Err(report!(errors::ConnectorError::MissingRequiredField { + field_name: "order_details" + })), + } + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } @@ -240,53 +356,82 @@ fn get_address_info( }) } -impl TryFrom> +impl TryFrom> for types::PaymentsAuthorizeRouterData { type Error = error_stack::Report; + fn try_from( - item: types::PaymentsResponseRouterData, + item: types::PaymentsResponseRouterData, ) -> Result { - let connector_response = types::ConnectorResponseData::with_additional_payment_method_data( - match item.response.authorized_payment_method { - Some(authorized_payment_method) => { - types::AdditionalPaymentMethodConnectorResponse::from(authorized_payment_method) - } - None => { - types::AdditionalPaymentMethodConnectorResponse::PayLater { klarna_sdk: None } - } - }, - ); - - Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some(item.response.order_id.clone()), - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + KlarnaAuthResponse::KlarnaPaymentsAuthResponse(ref response) => { + let connector_response = + response + .authorized_payment_method + .as_ref() + .map(|authorized_payment_method| { + types::ConnectorResponseData::with_additional_payment_method_data( + types::AdditionalPaymentMethodConnectorResponse::from( + authorized_payment_method.clone(), + ), + ) + }); + + Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + status: enums::AttemptStatus::foreign_from(( + response.fraud_status.clone(), + item.data.request.is_auto_capture()?, + )), + connector_response, + ..item.data + }) + } + KlarnaAuthResponse::KlarnaCheckoutAuthResponse(ref response) => Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(Some(RedirectForm::Html { + html_data: response.html_snippet.clone(), + })), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + status: enums::AttemptStatus::foreign_from(( + response.status.clone(), + item.data.request.is_auto_capture()?, + )), + connector_response: None, + ..item.data }), - status: enums::AttemptStatus::foreign_from(( - item.response.fraud_status, - item.data.request.is_auto_capture()?, - )), - connector_response: Some(connector_response), - ..item.data - }) + } } } - -#[derive(Debug, Serialize)] +#[derive(Default, Debug, Serialize, Deserialize)] pub struct OrderLines { name: String, quantity: u16, unit_price: MinorUnit, total_amount: MinorUnit, + total_tax_amount: Option, + tax_rate: Option, } #[derive(Debug, Serialize)] @@ -325,6 +470,13 @@ pub enum KlarnaFraudStatus { Rejected, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum KlarnaCheckoutStatus { + CheckoutComplete, + CheckoutIncomplete, +} + impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus { fn foreign_from((klarna_status, is_auto_capture): (KlarnaFraudStatus, bool)) -> Self { match klarna_status { @@ -341,13 +493,42 @@ impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus { } } +impl ForeignFrom<(KlarnaCheckoutStatus, bool)> for enums::AttemptStatus { + fn foreign_from((klarna_status, is_auto_capture): (KlarnaCheckoutStatus, bool)) -> Self { + match klarna_status { + KlarnaCheckoutStatus::CheckoutIncomplete => { + if is_auto_capture { + Self::AuthenticationPending + } else { + Self::Authorized + } + } + KlarnaCheckoutStatus::CheckoutComplete => Self::Charged, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum KlarnaPsyncResponse { + KlarnaSDKPsyncResponse(KlarnaSDKSyncResponse), + KlarnaCheckoutPSyncResponse(KlarnaCheckoutSyncResponse), +} + #[derive(Debug, Serialize, Deserialize)] -pub struct KlarnaPsyncResponse { +pub struct KlarnaSDKSyncResponse { pub order_id: String, pub status: KlarnaPaymentStatus, pub klarna_reference: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub struct KlarnaCheckoutSyncResponse { + pub order_id: String, + pub status: KlarnaCheckoutStatus, + pub options: CheckoutOptions, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KlarnaPaymentStatus { @@ -379,25 +560,45 @@ impl fn try_from( item: types::ResponseRouterData, ) -> Result { - Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .klarna_reference - .or(Some(item.response.order_id)), - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + KlarnaPsyncResponse::KlarnaSDKPsyncResponse(response) => Ok(Self { + status: enums::AttemptStatus::from(response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: response + .klarna_reference + .or(Some(response.order_id.clone())), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data }), - ..item.data - }) + KlarnaPsyncResponse::KlarnaCheckoutPSyncResponse(response) => Ok(Self { + status: enums::AttemptStatus::foreign_from(( + response.status.clone(), + response.options.auto_capture, + )), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + } } } diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index 121ec1d7b7ce..a4195655ed38 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -977,6 +977,7 @@ where get_pay_later_info(AlternativePaymentMethodType::AfterPay, item) } domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 7b6b67c8408e..0e88ae236656 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -579,6 +579,7 @@ impl merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }, ), connector: "payme".to_string(), diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 11e01b78e134..67204d9673c8 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -469,6 +469,21 @@ pub struct PaypalRedirectionResponse { attributes: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EpsRedirectionResponse { + name: Option>, + country_code: Option, + bic: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct IdealRedirectionResponse { + name: Option>, + country_code: Option, + bic: Option>, + iban_last_chars: Option>, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AttributeResponse { vault: PaypalVaultResponse, @@ -518,6 +533,8 @@ pub struct CardVaultResponse { pub enum PaymentSourceItemResponse { Card(CardVaultResponse), Paypal(PaypalRedirectionResponse), + Eps(EpsRedirectionResponse), + Ideal(IdealRedirectionResponse), } #[derive(Debug, Serialize)] @@ -1256,6 +1273,7 @@ impl TryFrom<&domain::PayLaterData> for PaypalPaymentsRequest { match value { domain::PayLaterData::KlarnaRedirect { .. } | domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::AfterpayClearpayRedirect { .. } | domain::PayLaterData::PayBrightRedirect {} @@ -1825,6 +1843,8 @@ impl PaymentSourceItemResponse::Card(card) => { card.attributes.map(|attr| attr.vault.id) } + PaymentSourceItemResponse::Eps(_) + | PaymentSourceItemResponse::Ideal(_) => None, }, None => None, }, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 2f0b0e0316fd..262b0348b5ae 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -992,6 +992,7 @@ impl TryFrom<&domain::payments::PayLaterData> for StripePaymentMethodType { } domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} | domain::PayLaterData::AlmaRedirect {} diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index dafca256529e..c20cc1aeb3aa 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1231,6 +1231,7 @@ pub fn get_apple_pay_session( merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }), connector: "trustpay".to_string(), delayed_session_token: true, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 1ea7565f69fe..171f19bc70ab 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2793,6 +2793,7 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, + KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -2916,6 +2917,7 @@ impl From for PaymentMethodDataType { domain::payments::PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { domain::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, domain::payments::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, + domain::payments::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout, domain::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index f9522b04d95c..498a00c240df 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -132,6 +132,7 @@ pub async fn form_payment_link_data( background_image: None, details_layout: None, branding_visibility: None, + payment_button_text: None, } }; @@ -277,6 +278,7 @@ pub async fn form_payment_link_data( background_image: payment_link_config.background_image.clone(), details_layout: payment_link_config.details_layout, branding_visibility: payment_link_config.branding_visibility, + payment_button_text: payment_link_config.payment_button_text.clone(), }; Ok(( @@ -335,6 +337,7 @@ pub async fn initiate_secure_payment_link_flow( hide_card_nickname_field: payment_link_config.hide_card_nickname_field, show_card_form_by_default: payment_link_config.show_card_form_by_default, payment_link_details: *link_details.to_owned(), + payment_button_text: payment_link_config.payment_button_text, }; let js_script = format!( "window.__PAYMENT_DETAILS = {}", @@ -680,6 +683,18 @@ pub fn get_payment_link_config_based_on_priority( .map(|background_image| background_image.clone().foreign_into()) }) }), + payment_button_text: payment_create_link_config + .as_ref() + .and_then(|payment_link_config| { + payment_link_config.theme_config.payment_button_text.clone() + }) + .or_else(|| { + business_theme_configs + .as_ref() + .and_then(|business_theme_config| { + business_theme_config.payment_button_text.clone() + }) + }), }; Ok((payment_link_config, domain_name)) @@ -786,6 +801,7 @@ pub async fn get_payment_link_status( background_image: None, details_layout: None, branding_visibility: None, + payment_button_text: None, } }; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js index 4bcdeed434f5..378900ba9ae1 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js @@ -303,7 +303,7 @@ function initializeEventListeners(paymentDetails) { var payNowButtonText = document.createElement("div"); var payNowButtonText = document.getElementById('submit-button-text'); if (payNowButtonText) { - payNowButtonText.textContent = translations.payNow; + payNowButtonText.textContent = paymentDetails.payment_button_text || translations.payNow; } if (submitButtonNode instanceof HTMLButtonElement) { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 7d6a1d803601..384bc09f1c4c 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -37,6 +37,7 @@ use error_stack::{report, ResultExt}; use events::EventInfo; use futures::future::join_all; use helpers::{decrypt_paze_token, ApplePayData}; +use hyperswitch_domain_models::payments::payment_intent::CustomerData; #[cfg(feature = "v2")] use hyperswitch_domain_models::payments::{ PaymentConfirmData, PaymentIntentData, PaymentStatusData, @@ -1786,6 +1787,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { json_payload: Some(req.json_payload.unwrap_or(serde_json::json!({})).into()), }), search_tags: None, + apple_pay_recurring_details: None, }), ..Default::default() }; @@ -2247,6 +2249,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { ), }), search_tags: None, + apple_pay_recurring_details: None, }), ..Default::default() }; @@ -3405,7 +3408,7 @@ pub async fn get_session_token_for_click_to_pay( payment_intent: &hyperswitch_domain_models::payments::PaymentIntent, ) -> RouterResult { use common_utils::types::AmountConvertor; - use hyperswitch_domain_models::payments::ClickToPayMetaData; + use hyperswitch_domain_models::payments::{payment_intent::CustomerData, ClickToPayMetaData}; use crate::consts::CLICK_TO_PAY; @@ -3442,6 +3445,19 @@ pub async fn get_session_token_for_click_to_pay( .change_context(errors::ApiErrorResponse::PreconditionFailed { message: "Failed to convert amount to string major unit for clickToPay".to_string(), })?; + + let customer_details_value = payment_intent + .customer_details + .clone() + .get_required_value("customer_details")?; + + let customer_details: CustomerData = customer_details_value + .parse_value("CustomerData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while parsing customer data from payment intent")?; + + validate_customer_details_for_click_to_pay(&customer_details)?; + Ok(api_models::payments::SessionToken::ClickToPay(Box::new( api_models::payments::ClickToPaySessionResponse { dpa_id: click_to_pay_metadata.dpa_id, @@ -3454,10 +3470,38 @@ pub async fn get_session_token_for_click_to_pay( merchant_country_code: click_to_pay_metadata.merchant_country_code, transaction_amount, transaction_currency_code: transaction_currency, + phone_number: customer_details.phone.clone(), + email: customer_details.email.clone(), + phone_country_code: customer_details.phone_country_code.clone(), }, ))) } +fn validate_customer_details_for_click_to_pay(customer_details: &CustomerData) -> RouterResult<()> { + match ( + customer_details.phone.as_ref(), + customer_details.phone_country_code.as_ref(), + customer_details.email.as_ref() + ) { + (None, None, Some(_)) => Ok(()), + (Some(_), Some(_), Some(_)) => Ok(()), + (Some(_), Some(_), None) => Ok(()), + (Some(_), None, Some(_)) => Ok(()), + (None, Some(_), None) => Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "phone", + }) + .attach_printable("phone number is not present in payment_intent.customer_details"), + (Some(_), None, None) => Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "phone_country_code", + }) + .attach_printable("phone_country_code is not present in payment_intent.customer_details"), + (_, _, _) => Err(errors::ApiErrorResponse::MissingRequiredFields { + field_names: vec!["phone", "phone_country_code", "email"], + }) + .attach_printable("either of phone, phone_country_code or email is not present in payment_intent.customer_details"), + } +} + #[cfg(feature = "v1")] pub async fn call_create_connector_customer_if_required( state: &SessionState, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 464781f3d7cc..1e3432220ac1 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -715,6 +715,7 @@ fn get_apple_pay_payment_request( merchant_identifier: Some(merchant_identifier.to_string()), required_billing_contact_fields, required_shipping_contact_fields, + recurring_payment_request: session_data.apple_pay_recurring_details, }; Ok(applepay_payment_request) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index cb0a606d2fc5..729b5115225f 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1521,6 +1521,7 @@ impl UpdateTracker, api::PaymentsRequest> for surcharge_amount, tax_amount, ), + connector_mandate_detail: payment_data .payment_attempt .connector_mandate_detail, diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index a316d6edc372..36b338f2d528 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1428,6 +1428,15 @@ impl PaymentCreate { let skip_external_tax_calculation = request.skip_external_tax_calculation; + let tax_details = request + .order_tax_amount + .map(|tax_amount| diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { + order_tax_amount: tax_amount, + }), + payment_method_type: None, + }); + Ok(storage::PaymentIntent { payment_id: payment_id.to_owned(), merchant_id: merchant_account.get_id().to_owned(), @@ -1482,7 +1491,7 @@ impl PaymentCreate { is_payment_processor_token_flow, organization_id: merchant_account.organization_id.clone(), shipping_cost: request.shipping_cost, - tax_details: None, + tax_details, skip_external_tax_calculation, psd2_sca_exemption_type: request.psd2_sca_exemption_type, }) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 85bc51a09a7f..15f01074c97e 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -8,7 +8,10 @@ use common_enums::{Currency, RequestIncrementalAuthorization}; use common_utils::{ consts::X_HS_LATENCY, fp_utils, pii, - types::{self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnitForConnector}, + types::{ + self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnit, + StringMajorUnitForConnector, + }, }; use diesel_models::{ ephemeral_key, @@ -259,6 +262,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( .net_amount .get_amount_as_i64(), minor_amount: payment_data.payment_attempt.amount_details.net_amount, + order_tax_amount: None, currency: payment_data.payment_intent.amount_details.currency, browser_info: None, email: None, @@ -558,6 +562,25 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( .map(|order_detail| order_detail.expose()) .collect() }); + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert( + payment_data.payment_intent.amount_details.order_amount, + payment_data.payment_intent.amount_details.currency, + ) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount)) + }); + // TODO: few fields are repeated in both routerdata and request let request = types::PaymentsSessionData { amount: payment_data @@ -581,6 +604,7 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( order_details, email, minor_amount: payment_data.payment_intent.amount_details.order_amount, + apple_pay_recurring_details, }; // TODO: evaluate the fields in router data, if they are required or not @@ -2427,25 +2451,6 @@ pub fn mobile_payment_next_steps_check( Ok(mobile_payment_next_step) } -pub fn change_order_details_to_new_type( - order_amount: MinorUnit, - order_details: api_models::payments::OrderDetails, -) -> Option> { - Some(vec![api_models::payments::OrderDetailsWithAmount { - product_name: order_details.product_name, - quantity: order_details.quantity, - amount: order_amount, - product_img_link: order_details.product_img_link, - requires_shipping: order_details.requires_shipping, - product_id: order_details.product_id, - category: order_details.category, - sub_category: order_details.sub_category, - brand: order_details.brand, - product_type: order_details.product_type, - product_tax_code: order_details.product_tax_code, - }]) -} - impl ForeignFrom for api_models::payments::NextActionData { fn foreign_from(qr_info: api_models::payments::QrCodeInformation) -> Self { match qr_info { @@ -2625,6 +2630,10 @@ impl TryFrom> for types::PaymentsAuthoriz statement_descriptor: payment_data.payment_intent.statement_descriptor_name, capture_method: payment_data.payment_attempt.capture_method, amount: amount.get_amount_as_i64(), + order_tax_amount: payment_data + .payment_attempt + .net_amount + .get_order_tax_amount(), minor_amount: amount, currency: payment_data.currency, browser_info, @@ -2961,7 +2970,6 @@ impl TryFrom> for types::SdkPaymentsSessi #[cfg(feature = "v1")] impl TryFrom> for types::SdkPaymentsSessionUpdateData { type Error = error_stack::Report; - fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; let order_tax_amount = payment_data @@ -3097,6 +3105,22 @@ impl TryFrom> for types::PaymentsSessionD // net_amount here would include amount, surcharge_amount and shipping_cost let net_amount = amount + surcharge_amount + shipping_cost; + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert(net_amount, payment_data.currency) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignInto::foreign_into((apple_pay_recurring_details, apple_pay_amount)) + }); + Ok(Self { amount: amount.get_amount_as_i64(), //need to change once we move to connector module minor_amount: amount, @@ -3112,6 +3136,7 @@ impl TryFrom> for types::PaymentsSessionD order_details, surcharge_details: payment_data.surcharge_details, email: payment_data.email, + apple_pay_recurring_details, }) } } @@ -3158,6 +3183,29 @@ impl TryFrom> for types::PaymentsSessionD // net_amount here would include amount, surcharge_amount and shipping_cost let net_amount = amount + surcharge_amount + shipping_cost; + let required_amount_type = StringMajorUnitForConnector; + + let apple_pay_amount = required_amount_type + .convert(net_amount, payment_data.currency) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for applePay".to_string(), + })?; + + let apple_pay_recurring_details = payment_data + .payment_intent + .feature_metadata + .map(|feature_metadata| { + feature_metadata + .parse_value::("FeatureMetadata") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed parsing FeatureMetadata") + }) + .transpose()? + .and_then(|feature_metadata| feature_metadata.apple_pay_recurring_details) + .map(|apple_pay_recurring_details| { + ForeignFrom::foreign_from((apple_pay_recurring_details, apple_pay_amount)) + }); + Ok(Self { amount: net_amount.get_amount_as_i64(), //need to change once we move to connector module minor_amount: amount, @@ -3173,10 +3221,99 @@ impl TryFrom> for types::PaymentsSessionD order_details, email: payment_data.email, surcharge_details: payment_data.surcharge_details, + apple_pay_recurring_details, }) } } +impl + ForeignFrom<( + diesel_models::types::ApplePayRecurringDetails, + StringMajorUnit, + )> for api_models::payments::ApplePayRecurringPaymentRequest +{ + fn foreign_from( + (apple_pay_recurring_details, net_amount): ( + diesel_models::types::ApplePayRecurringDetails, + StringMajorUnit, + ), + ) -> Self { + Self { + payment_description: apple_pay_recurring_details.payment_description, + regular_billing: api_models::payments::ApplePayRegularBillingRequest { + amount: net_amount, + label: apple_pay_recurring_details.regular_billing.label, + payment_timing: api_models::payments::ApplePayPaymentTiming::Recurring, + recurring_payment_start_date: apple_pay_recurring_details + .regular_billing + .recurring_payment_start_date, + recurring_payment_end_date: apple_pay_recurring_details + .regular_billing + .recurring_payment_end_date, + recurring_payment_interval_unit: apple_pay_recurring_details + .regular_billing + .recurring_payment_interval_unit + .map(ForeignFrom::foreign_from), + recurring_payment_interval_count: apple_pay_recurring_details + .regular_billing + .recurring_payment_interval_count, + }, + billing_agreement: apple_pay_recurring_details.billing_agreement, + management_url: apple_pay_recurring_details.management_url, + } + } +} + +impl ForeignFrom + for api_models::payments::ApplePayRecurringDetails +{ + fn foreign_from( + apple_pay_recurring_details: diesel_models::types::ApplePayRecurringDetails, + ) -> Self { + Self { + payment_description: apple_pay_recurring_details.payment_description, + regular_billing: ForeignFrom::foreign_from(apple_pay_recurring_details.regular_billing), + billing_agreement: apple_pay_recurring_details.billing_agreement, + management_url: apple_pay_recurring_details.management_url, + } + } +} + +impl ForeignFrom + for api_models::payments::ApplePayRegularBillingDetails +{ + fn foreign_from( + apple_pay_regular_billing: diesel_models::types::ApplePayRegularBillingDetails, + ) -> Self { + Self { + label: apple_pay_regular_billing.label, + recurring_payment_start_date: apple_pay_regular_billing.recurring_payment_start_date, + recurring_payment_end_date: apple_pay_regular_billing.recurring_payment_end_date, + recurring_payment_interval_unit: apple_pay_regular_billing + .recurring_payment_interval_unit + .map(ForeignFrom::foreign_from), + recurring_payment_interval_count: apple_pay_regular_billing + .recurring_payment_interval_count, + } + } +} + +impl ForeignFrom + for api_models::payments::RecurringPaymentIntervalUnit +{ + fn foreign_from( + apple_pay_recurring_payment_interval_unit: diesel_models::types::RecurringPaymentIntervalUnit, + ) -> Self { + match apple_pay_recurring_payment_interval_unit { + diesel_models::types::RecurringPaymentIntervalUnit::Day => Self::Day, + diesel_models::types::RecurringPaymentIntervalUnit::Month => Self::Month, + diesel_models::types::RecurringPaymentIntervalUnit::Year => Self::Year, + diesel_models::types::RecurringPaymentIntervalUnit::Hour => Self::Hour, + diesel_models::types::RecurringPaymentIntervalUnit::Minute => Self::Minute, + } + } +} + #[cfg(feature = "v1")] impl TryFrom> for types::SetupMandateRequestData { type Error = error_stack::Report; @@ -3645,6 +3782,7 @@ impl ForeignFrom background_image.clone(), ) }), + payment_button_text: config.payment_button_text, } } } @@ -3707,6 +3845,7 @@ impl ForeignFrom background_image.clone(), ) }), + payment_button_text: config.payment_button_text, } } } diff --git a/crates/router/src/core/recon.rs b/crates/router/src/core/recon.rs index c2f289d4cc02..3b2aacd4514d 100644 --- a/crates/router/src/core/recon.rs +++ b/crates/router/src/core/recon.rs @@ -80,6 +80,7 @@ pub async fn send_recon_request( state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -179,7 +180,7 @@ pub async fn recon_merchant_account_update( let theme = theme_utils::get_most_specific_theme_using_lineage( &state.clone(), ThemeLineage::Merchant { - tenant_id: state.tenant.tenant_id, + tenant_id: state.tenant.tenant_id.clone(), org_id: auth.merchant_account.get_org_id().clone(), merchant_id: merchant_id.clone(), }, @@ -210,6 +211,7 @@ pub async fn recon_merchant_account_update( let _ = state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 642473bdf8e0..19f257ef4f37 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -96,6 +96,7 @@ pub async fn signup_with_merchant_id( let send_email_result = state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -239,6 +240,7 @@ pub async fn connect_account( let send_email_result = state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -294,6 +296,7 @@ pub async fn connect_account( let magic_link_result = state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(magic_link_email), state.conf.proxy.https_url.as_ref(), ) @@ -310,6 +313,7 @@ pub async fn connect_account( let welcome_email_result = state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(welcome_to_community_email), state.conf.proxy.https_url.as_ref(), ) @@ -438,6 +442,7 @@ pub async fn forgot_password( state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -845,6 +850,7 @@ async fn handle_existing_user_invitation( is_email_sent = state .email_client .compose_and_send_email( + email_types::get_base_url(state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -1000,6 +1006,7 @@ async fn handle_new_user_invitation( let send_email_result = state .email_client .compose_and_send_email( + email_types::get_base_url(state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -1151,6 +1158,7 @@ pub async fn resend_invite( state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -1782,6 +1790,7 @@ pub async fn send_verification_mail( state .email_client .compose_and_send_email( + email_types::get_base_url(&state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) diff --git a/crates/router/src/core/user/dashboard_metadata.rs b/crates/router/src/core/user/dashboard_metadata.rs index 88380363b4e3..aa67a70223ff 100644 --- a/crates/router/src/core/user/dashboard_metadata.rs +++ b/crates/router/src/core/user/dashboard_metadata.rs @@ -496,6 +496,7 @@ async fn insert_metadata( let send_email_result = state .email_client .compose_and_send_email( + email_types::get_base_url(state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) diff --git a/crates/router/src/core/user/theme.rs b/crates/router/src/core/user/theme.rs index 27b342d90538..2a896b7158f7 100644 --- a/crates/router/src/core/user/theme.rs +++ b/crates/router/src/core/user/theme.rs @@ -21,7 +21,7 @@ pub async fn get_theme_using_lineage( lineage: ThemeLineage, ) -> UserResponse { let theme = state - .global_store + .store .find_theme_by_lineage(lineage) .await .to_not_found_response(UserErrors::ThemeNotFound)?; @@ -55,7 +55,7 @@ pub async fn get_theme_using_theme_id( theme_id: String, ) -> UserResponse { let theme = state - .global_store + .store .find_theme_by_theme_id(theme_id.clone()) .await .to_not_found_response(UserErrors::ThemeNotFound)?; @@ -90,7 +90,7 @@ pub async fn upload_file_to_theme_storage( request: theme_api::UploadFileRequest, ) -> UserResponse<()> { let db_theme = state - .global_store + .store .find_theme_by_lineage(request.lineage) .await .to_not_found_response(UserErrors::ThemeNotFound)?; @@ -131,7 +131,7 @@ pub async fn create_theme( ); let db_theme = state - .global_store + .store .insert_theme(new_theme) .await .to_duplicate_response(UserErrors::ThemeAlreadyExists)?; @@ -176,7 +176,7 @@ pub async fn update_theme( request: theme_api::UpdateThemeRequest, ) -> UserResponse { let db_theme = state - .global_store + .store .find_theme_by_lineage(request.lineage) .await .to_not_found_response(UserErrors::ThemeNotFound)?; @@ -225,7 +225,7 @@ pub async fn delete_theme( lineage: ThemeLineage, ) -> UserResponse<()> { state - .global_store + .store .delete_theme_by_lineage_and_theme_id(theme_id.clone(), lineage) .await .to_not_found_response(UserErrors::ThemeNotFound)?; diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 714bf9fed3cc..e897e1b336a2 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -86,7 +86,7 @@ pub async fn create_role( } let role = state - .store + .global_store .insert_role(RoleNew { role_id: generate_id_with_default_len("role"), role_name: role_name.get_role_name(), @@ -220,7 +220,7 @@ pub async fn update_role( } let updated_role = state - .store + .global_store .update_role_by_role_id( role_id, RoleUpdate::UpdateDetails { @@ -271,7 +271,7 @@ pub async fn list_roles_with_info( let custom_roles = match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? { EntityType::Tenant | EntityType::Organization => state - .store + .global_store .list_roles_for_org_by_parameters( &user_from_token.org_id, None, @@ -282,7 +282,7 @@ pub async fn list_roles_with_info( .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get roles")?, EntityType::Merchant => state - .store + .global_store .list_roles_for_org_by_parameters( &user_from_token.org_id, Some(&user_from_token.merchant_id), @@ -344,7 +344,7 @@ pub async fn list_roles_at_entity_level( let custom_roles = match req.entity_type { EntityType::Tenant | EntityType::Organization => state - .store + .global_store .list_roles_for_org_by_parameters( &user_from_token.org_id, None, @@ -356,7 +356,7 @@ pub async fn list_roles_at_entity_level( .attach_printable("Failed to get roles")?, EntityType::Merchant => state - .store + .global_store .list_roles_for_org_by_parameters( &user_from_token.org_id, Some(&user_from_token.merchant_id), diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index cc9a1e2f436d..fbb75b6af603 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -128,10 +128,10 @@ pub trait StorageInterface: + authorization::AuthorizationInterface + user::sample_data::BatchSampleDataInterface + health_check::HealthCheckDbInterface - + role::RoleInterface + user_authentication_method::UserAuthenticationMethodInterface + authentication::AuthenticationInterface + generic_link::GenericLinkInterface + + user::theme::ThemeInterface + 'static { fn get_scheduler_db(&self) -> Box; @@ -147,7 +147,7 @@ pub trait GlobalStorageInterface: + user::UserInterface + user_role::UserRoleInterface + user_key_store::UserKeyStoreInterface - + user::theme::ThemeInterface + + role::RoleInterface + 'static { } diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index 2b7e8bd73406..da296373d802 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -73,7 +73,8 @@ where A: SessionStateInfo + Sync, { state - .store() + .session_state() + .global_store .find_by_role_id_and_org_id(role_id, org_id) .await .map(roles::RoleInfo::from) diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs index dcffa3107c91..c9c64b76143d 100644 --- a/crates/router/src/services/authorization/roles.rs +++ b/crates/router/src/services/authorization/roles.rs @@ -126,7 +126,7 @@ impl RoleInfo { Ok(role.clone()) } else { state - .store + .global_store .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id) .await .map(Self::from) @@ -142,7 +142,7 @@ impl RoleInfo { Ok(role.clone()) } else { state - .store + .global_store .find_by_role_id_and_org_id(role_id, org_id) .await .map(Self::from) diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 128250a61aa8..476cd1292f6a 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -225,6 +225,14 @@ pub fn get_link_with_token( email_url } +pub fn get_base_url(state: &SessionState) -> &str { + if !state.conf.multitenancy.enabled { + &state.conf.user.base_url + } else { + &state.tenant.user.control_center_url + } +} + pub struct VerifyEmail { pub recipient_email: domain::UserEmail, pub settings: std::sync::Arc, @@ -237,7 +245,7 @@ pub struct VerifyEmail { /// Currently only HTML is supported #[async_trait::async_trait] impl EmailData for VerifyEmail { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -248,7 +256,7 @@ impl EmailData for VerifyEmail { .change_context(EmailError::TokenGenerationFailure)?; let verify_email_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "verify_email", &self.auth_id, @@ -279,7 +287,7 @@ pub struct ResetPassword { #[async_trait::async_trait] impl EmailData for ResetPassword { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -290,7 +298,7 @@ impl EmailData for ResetPassword { .change_context(EmailError::TokenGenerationFailure)?; let reset_password_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "set_password", &self.auth_id, @@ -322,7 +330,7 @@ pub struct MagicLink { #[async_trait::async_trait] impl EmailData for MagicLink { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -333,7 +341,7 @@ impl EmailData for MagicLink { .change_context(EmailError::TokenGenerationFailure)?; let magic_link_login = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "verify_email", &self.auth_id, @@ -366,7 +374,7 @@ pub struct InviteUser { #[async_trait::async_trait] impl EmailData for InviteUser { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), Some(self.entity.clone()), @@ -377,7 +385,7 @@ impl EmailData for InviteUser { .change_context(EmailError::TokenGenerationFailure)?; let invite_user_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "accept_invite_from_email", &self.auth_id, @@ -406,7 +414,7 @@ pub struct ReconActivation { #[async_trait::async_trait] impl EmailData for ReconActivation { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let body = html::get_html_body(EmailBody::ReconActivation { user_name: self.user_name.clone().get_secret().expose(), }); @@ -461,7 +469,7 @@ impl BizEmailProd { #[async_trait::async_trait] impl EmailData for BizEmailProd { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let body = html::get_html_body(EmailBody::BizEmailProd { user_name: self.user_name.clone().expose(), poc_email: self.poc_email.clone().expose(), @@ -491,7 +499,7 @@ pub struct ProFeatureRequest { #[async_trait::async_trait] impl EmailData for ProFeatureRequest { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let recipient = self.recipient_email.clone().into_inner(); let body = html::get_html_body(EmailBody::ProFeatureRequest { @@ -521,7 +529,7 @@ pub struct ApiKeyExpiryReminder { #[async_trait::async_trait] impl EmailData for ApiKeyExpiryReminder { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let recipient = self.recipient_email.clone().into_inner(); let body = html::get_html_body(EmailBody::ApiKeyExpiryReminder { @@ -545,7 +553,7 @@ pub struct WelcomeToCommunity { #[async_trait::async_trait] impl EmailData for WelcomeToCommunity { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let body = html::get_html_body(EmailBody::WelcomeToCommunity); Ok(EmailContents { diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 2ab433785160..de4c063c41aa 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -873,6 +873,7 @@ impl ForeignFrom<&SetupMandateRouterData> for PaymentsAuthorizeData { email: data.request.email.clone(), customer_name: data.request.customer_name.clone(), amount: 0, + order_tax_amount: Some(MinorUnit::zero()), minor_amount: MinorUnit::new(0), statement_descriptor: None, capture_method: None, diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 3bd318731313..bd55bd96b96e 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -29,6 +29,7 @@ impl VerifyConnectorData { amount: 1000, minor_amount: common_utils::types::MinorUnit::new(1000), confirm: true, + order_tax_amount: None, currency: storage_enums::Currency::USD, metadata: None, mandate_id: None, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index d4cd57754362..1947a2a2dee1 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1239,6 +1239,7 @@ impl ForeignFrom for payments::PaymentAttemptResponse { attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, amount: payment_attempt.net_amount.get_order_amount(), + order_tax_amount: payment_attempt.net_amount.get_order_tax_amount(), currency: payment_attempt.currency, connector: payment_attempt.connector, error_message: payment_attempt.error_reason, @@ -1966,6 +1967,7 @@ impl ForeignFrom background_image: item .background_image .map(|background_image| background_image.foreign_into()), + payment_button_text: item.payment_button_text, } } } @@ -1988,6 +1990,7 @@ impl ForeignFrom background_image: item .background_image .map(|background_image| background_image.foreign_into()), + payment_button_text: item.payment_button_text, } } } diff --git a/crates/router/src/utils/user/theme.rs b/crates/router/src/utils/user/theme.rs index 1c8b76989ae8..9cc1c43462c3 100644 --- a/crates/router/src/utils/user/theme.rs +++ b/crates/router/src/utils/user/theme.rs @@ -190,7 +190,7 @@ pub async fn get_most_specific_theme_using_lineage( lineage: ThemeLineage, ) -> UserResult> { match state - .global_store + .store .find_most_specific_theme_in_lineage(lineage) .await { @@ -210,7 +210,7 @@ pub async fn get_theme_using_optional_theme_id( theme_id: Option, ) -> UserResult> { match theme_id - .async_map(|theme_id| state.global_store.find_theme_by_theme_id(theme_id)) + .async_map(|theme_id| state.store.find_theme_by_theme_id(theme_id)) .await .transpose() { diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index b8f93bff943c..ac8ee11fc6a2 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -57,7 +57,7 @@ pub async fn validate_role_name( // TODO: Create and use find_by_role_name to make this efficient let is_present_in_custom_roles = state - .store + .global_store .list_all_roles(merchant_id, org_id) .await .change_context(UserErrors::InternalServerError)? diff --git a/crates/router/src/workflows/api_key_expiry.rs b/crates/router/src/workflows/api_key_expiry.rs index cdc4959c4562..2ff527a046e5 100644 --- a/crates/router/src/workflows/api_key_expiry.rs +++ b/crates/router/src/workflows/api_key_expiry.rs @@ -9,7 +9,7 @@ use crate::{ consts, errors, logger::error, routes::{metrics, SessionState}, - services::email::types::ApiKeyExpiryReminder, + services::email::types::{self as email_types, ApiKeyExpiryReminder}, types::{api, domain::UserEmail, storage}, utils::{user::theme as theme_utils, OptionExt}, }; @@ -110,6 +110,7 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { .email_client .clone() .compose_and_send_email( + email_types::get_base_url(state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index 1994acb6a659..15f980e242e1 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -90,6 +90,8 @@ fn payment_method_details() -> Option { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -391,6 +393,8 @@ async fn should_fail_payment_for_incorrect_cvc() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -431,6 +435,8 @@ async fn should_fail_payment_for_invalid_exp_month() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -471,6 +477,8 @@ async fn should_fail_payment_for_incorrect_expiry_year() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_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 f195aeaf7c40..489b55a227b3 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -939,6 +939,7 @@ impl Default for PaymentAuthorizeType { payment_method_data: types::domain::PaymentMethodData::Card(CCardType::default().0), amount: 100, minor_amount: MinorUnit::new(100), + order_tax_amount: Some(MinorUnit::zero()), currency: enums::Currency::USD, confirm: true, statement_descriptor_suffix: None, diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index de60c5e8d86a..a13fe48d2559 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -334,6 +334,8 @@ async fn should_fail_payment_for_incorrect_card_number() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_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()), @@ -377,6 +379,8 @@ async fn should_fail_payment_for_incorrect_cvc() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_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()), @@ -420,6 +424,8 @@ async fn should_fail_payment_for_invalid_exp_month() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_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()), @@ -463,6 +469,8 @@ async fn should_fail_payment_for_incorrect_expiry_year() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_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/loadtest/config/development.toml b/loadtest/config/development.toml index 51981a298e31..09b52bc357d2 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -385,8 +385,14 @@ keys = "accept-language,user-agent" enabled = false global_tenant = { schema = "public", redis_key_prefix = "" } -[multitenancy.tenants] -public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [email] sender_email = "example@example.com"