diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 926af8c162..29dd0d3a96 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -5168,6 +5168,27 @@ } } }, + "ConnectorWalletDetails": { + "type": "object", + "properties": { + "apple_pay_combined": { + "type": "object", + "description": "This field contains the Apple Pay certificates and credentials for iOS and Web Apple Pay flow", + "nullable": true + }, + "apple_pay": { + "type": "object", + "description": "This field is for our legacy Apple Pay flow that contains the Apple Pay certificates and credentials for only iOS Apple Pay flow", + "nullable": true + }, + "samsung_pay": { + "type": "object", + "description": "This field contains the Samsung Pay certificates and credentials", + "nullable": true + } + }, + "additionalProperties": false + }, "CountryAlpha2": { "type": "string", "enum": [ @@ -8901,6 +8922,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -9097,6 +9126,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -9238,6 +9275,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -9363,6 +9408,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -17950,15 +18003,164 @@ } } }, + "SamsungPayAmountDetails": { + "type": "object", + "required": [ + "option", + "currency_code", + "total" + ], + "properties": { + "option": { + "$ref": "#/components/schemas/SamsungPayAmountFormat" + }, + "currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "total": { + "type": "string", + "description": "The total amount of the transaction", + "example": "38.02" + } + } + }, + "SamsungPayAmountFormat": { + "type": "string", + "enum": [ + "FORMAT_TOTAL_PRICE_ONLY", + "FORMAT_TOTAL_ESTIMATED_AMOUNT" + ] + }, + "SamsungPayMerchantPaymentInformation": { + "type": "object", + "required": [ + "name", + "url", + "country_code" + ], + "properties": { + "name": { + "type": "string", + "description": "Merchant name, this will be displayed on the Samsung Pay screen" + }, + "url": { + "type": "string", + "description": "Merchant domain that process payments" + }, + "country_code": { + "$ref": "#/components/schemas/CountryAlpha2" + } + } + }, + "SamsungPayProtocolType": { + "type": "string", + "enum": [ + "PROTOCOL3DS" + ] + }, + "SamsungPaySessionTokenResponse": { + "type": "object", + "required": [ + "version", + "service_id", + "order_number", + "merchant", + "amount", + "protocol", + "allowed_brands" + ], + "properties": { + "version": { + "type": "string", + "description": "Samsung Pay API version" + }, + "service_id": { + "type": "string", + "description": "Samsung Pay service ID to which session call needs to be made" + }, + "order_number": { + "type": "string", + "description": "Order number of the transaction" + }, + "merchant": { + "$ref": "#/components/schemas/SamsungPayMerchantPaymentInformation" + }, + "amount": { + "$ref": "#/components/schemas/SamsungPayAmountDetails" + }, + "protocol": { + "$ref": "#/components/schemas/SamsungPayProtocolType" + }, + "allowed_brands": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of supported card brands" + } + } + }, + "SamsungPayTokenData": { + "type": "object", + "required": [ + "version", + "data" + ], + "properties": { + "type": { + "type": "string", + "description": "3DS type used by Samsung Pay", + "nullable": true + }, + "version": { + "type": "string", + "description": "3DS version used by Samsung Pay" + }, + "data": { + "type": "string", + "description": "Samsung Pay encrypted payment credential data" + } + } + }, + "SamsungPayWalletCredentials": { + "type": "object", + "required": [ + "card_brand", + "card_last4digits", + "3_d_s" + ], + "properties": { + "method": { + "type": "string", + "description": "Specifies authentication method used", + "nullable": true + }, + "recurring_payment": { + "type": "boolean", + "description": "Value if credential is enabled for recurring payment", + "nullable": true + }, + "card_brand": { + "type": "string", + "description": "Brand of the payment card" + }, + "card_last4digits": { + "type": "string", + "description": "Last 4 digits of the card number" + }, + "3_d_s": { + "$ref": "#/components/schemas/SamsungPayTokenData" + } + } + }, "SamsungPayWalletData": { "type": "object", "required": [ - "token" + "payment_credential" ], "properties": { - "token": { - "type": "string", - "description": "The encrypted payment token from Samsung" + "payment_credential": { + "$ref": "#/components/schemas/SamsungPayWalletCredentials" } } }, @@ -18166,6 +18368,27 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/SamsungPaySessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "samsung_pay" + ] + } + } + } + ] + }, { "allOf": [ { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 2635cdb42f..404d93f0d2 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -8597,6 +8597,27 @@ } } }, + "ConnectorWalletDetails": { + "type": "object", + "properties": { + "apple_pay_combined": { + "type": "object", + "description": "This field contains the Apple Pay certificates and credentials for iOS and Web Apple Pay flow", + "nullable": true + }, + "apple_pay": { + "type": "object", + "description": "This field is for our legacy Apple Pay flow that contains the Apple Pay certificates and credentials for only iOS Apple Pay flow", + "nullable": true + }, + "samsung_pay": { + "type": "object", + "description": "This field contains the Samsung Pay certificates and credentials", + "nullable": true + } + }, + "additionalProperties": false + }, "CountryAlpha2": { "type": "string", "enum": [ @@ -12649,6 +12670,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -12876,6 +12905,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -13043,6 +13080,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -13167,6 +13212,14 @@ } ], "nullable": true + }, + "connector_wallets_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorWalletDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -22195,15 +22248,164 @@ } } }, + "SamsungPayAmountDetails": { + "type": "object", + "required": [ + "option", + "currency_code", + "total" + ], + "properties": { + "option": { + "$ref": "#/components/schemas/SamsungPayAmountFormat" + }, + "currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "total": { + "type": "string", + "description": "The total amount of the transaction", + "example": "38.02" + } + } + }, + "SamsungPayAmountFormat": { + "type": "string", + "enum": [ + "FORMAT_TOTAL_PRICE_ONLY", + "FORMAT_TOTAL_ESTIMATED_AMOUNT" + ] + }, + "SamsungPayMerchantPaymentInformation": { + "type": "object", + "required": [ + "name", + "url", + "country_code" + ], + "properties": { + "name": { + "type": "string", + "description": "Merchant name, this will be displayed on the Samsung Pay screen" + }, + "url": { + "type": "string", + "description": "Merchant domain that process payments" + }, + "country_code": { + "$ref": "#/components/schemas/CountryAlpha2" + } + } + }, + "SamsungPayProtocolType": { + "type": "string", + "enum": [ + "PROTOCOL3DS" + ] + }, + "SamsungPaySessionTokenResponse": { + "type": "object", + "required": [ + "version", + "service_id", + "order_number", + "merchant", + "amount", + "protocol", + "allowed_brands" + ], + "properties": { + "version": { + "type": "string", + "description": "Samsung Pay API version" + }, + "service_id": { + "type": "string", + "description": "Samsung Pay service ID to which session call needs to be made" + }, + "order_number": { + "type": "string", + "description": "Order number of the transaction" + }, + "merchant": { + "$ref": "#/components/schemas/SamsungPayMerchantPaymentInformation" + }, + "amount": { + "$ref": "#/components/schemas/SamsungPayAmountDetails" + }, + "protocol": { + "$ref": "#/components/schemas/SamsungPayProtocolType" + }, + "allowed_brands": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of supported card brands" + } + } + }, + "SamsungPayTokenData": { + "type": "object", + "required": [ + "version", + "data" + ], + "properties": { + "type": { + "type": "string", + "description": "3DS type used by Samsung Pay", + "nullable": true + }, + "version": { + "type": "string", + "description": "3DS version used by Samsung Pay" + }, + "data": { + "type": "string", + "description": "Samsung Pay encrypted payment credential data" + } + } + }, + "SamsungPayWalletCredentials": { + "type": "object", + "required": [ + "card_brand", + "card_last4digits", + "3_d_s" + ], + "properties": { + "method": { + "type": "string", + "description": "Specifies authentication method used", + "nullable": true + }, + "recurring_payment": { + "type": "boolean", + "description": "Value if credential is enabled for recurring payment", + "nullable": true + }, + "card_brand": { + "type": "string", + "description": "Brand of the payment card" + }, + "card_last4digits": { + "type": "string", + "description": "Last 4 digits of the card number" + }, + "3_d_s": { + "$ref": "#/components/schemas/SamsungPayTokenData" + } + } + }, "SamsungPayWalletData": { "type": "object", "required": [ - "token" + "payment_credential" ], "properties": { - "token": { - "type": "string", - "description": "The encrypted payment token from Samsung" + "payment_credential": { + "$ref": "#/components/schemas/SamsungPayWalletCredentials" } } }, @@ -22411,6 +22613,27 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/SamsungPaySessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "samsung_pay" + ] + } + } + } + ] + }, { "allOf": [ { diff --git a/config/config.example.toml b/config/config.example.toml index a6af984b01..861785dc34 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -534,6 +534,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.stax] credit = { currency = "USD" } diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index abb50a2c2f..3a54c5ef08 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -304,6 +304,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.volt] open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT,NL,PT,SI,SK,BG,CZ,DK,HU,NO,PL,RO,SE,AU,BR", currency = "EUR,GBP,DKK,NOK,PLN,SEK,AUD,BRL"} diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 38799c2385..ec3737a5c6 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -278,6 +278,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.braintree] paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 2217a72429..1c605ef35a 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -281,6 +281,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.braintree] paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" diff --git a/config/development.toml b/config/development.toml index 43130cb78f..6e56d5bb14 100644 --- a/config/development.toml +++ b/config/development.toml @@ -449,6 +449,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.braintree] paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 2c6b12a88a..15038da012 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -414,6 +414,7 @@ credit = { currency = "USD,GBP,EUR" } debit = { currency = "USD,GBP,EUR" } apple_pay = { currency = "USD,GBP,EUR" } google_pay = { currency = "USD,GBP,EUR" } +samsung_pay = { currency = "USD,GBP,EUR" } [pm_filters.helcim] credit = { currency = "USD" } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 5a9beb565d..6f65633956 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -745,6 +745,10 @@ pub struct MerchantConnectorCreate { /// In case the merchant needs to store any additional sensitive data #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v2")] @@ -882,6 +886,10 @@ pub struct MerchantConnectorCreate { /// In case the merchant needs to store any additional sensitive data #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v1")] @@ -1102,6 +1110,10 @@ pub struct MerchantConnectorResponse { #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v2")] @@ -1221,6 +1233,10 @@ pub struct MerchantConnectorResponse { #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v1")] @@ -1327,6 +1343,10 @@ pub struct MerchantConnectorListResponse { #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v1")] @@ -1417,6 +1437,10 @@ pub struct MerchantConnectorListResponse { #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, } #[cfg(feature = "v2")] @@ -1512,6 +1536,28 @@ pub struct MerchantConnectorUpdate { /// In case the merchant needs to store any additional sensitive data #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + #[schema(value_type = Option)] + pub connector_wallets_details: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] + +pub struct ConnectorWalletDetails { + /// This field contains the Apple Pay certificates and credentials for iOS and Web Apple Pay flow + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub apple_pay_combined: Option, + /// This field is for our legacy Apple Pay flow that contains the Apple Pay certificates and credentials for only iOS Apple Pay flow + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub apple_pay: Option, + /// This field contains the Samsung Pay certificates and credentials + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub samsung_pay: Option, } /// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc." @@ -1597,6 +1643,9 @@ pub struct MerchantConnectorUpdate { /// In case the merchant needs to store any additional sensitive data #[schema(value_type = Option)] pub additional_merchant_data: Option, + + /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials + pub connector_wallets_details: Option, } #[cfg(feature = "v2")] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index d1b658dab4..0236ee0c51 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2637,9 +2637,37 @@ impl GetAddressFromPaymentMethodData for WalletData { #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub struct SamsungPayWalletData { - /// The encrypted payment token from Samsung + pub payment_credential: SamsungPayWalletCredentials, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct SamsungPayWalletCredentials { + /// Specifies authentication method used + pub method: Option, + /// Value if credential is enabled for recurring payment + pub recurring_payment: Option, + /// Brand of the payment card + pub card_brand: String, + /// Last 4 digits of the card number + #[serde(rename = "card_last4digits")] + pub card_last_four_digits: String, + /// Samsung Pay token data + #[serde(rename = "3_d_s")] + pub token_data: SamsungPayTokenData, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct SamsungPayTokenData { + /// 3DS type used by Samsung Pay + #[serde(rename = "type")] + pub three_ds_type: Option, + /// 3DS version used by Samsung Pay + pub version: String, + /// Samsung Pay encrypted payment credential data #[schema(value_type = String)] - pub token: Secret, + pub data: Secret, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -4624,6 +4652,20 @@ pub struct GpaySessionTokenData { pub data: GpayMetaData, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SamsungPaySessionTokenData { + #[serde(rename = "samsung_pay")] + pub data: SamsungPayMetadata, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SamsungPayMetadata { + pub service_id: String, + pub merchant_display_name: String, + pub merchant_business_country: api_enums::CountryAlpha2, + pub allowed_brands: Vec, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaypalSdkMetaData { pub client_id: String, @@ -4789,6 +4831,8 @@ pub struct SessionTokenForSimplifiedApplePay { pub enum SessionToken { /// The session response structure for Google Pay GooglePay(Box), + /// The session response structure for Samsung Pay + SamsungPay(Box), /// The session response structure for Klarna Klarna(Box), /// The session response structure for PayPal @@ -4846,6 +4890,68 @@ pub struct GooglePaySessionResponse { pub secrets: Option, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct SamsungPaySessionTokenResponse { + /// Samsung Pay API version + pub version: String, + /// Samsung Pay service ID to which session call needs to be made + pub service_id: String, + /// Order number of the transaction + pub order_number: String, + /// Field containing merchant information + #[serde(rename = "merchant")] + pub merchant_payment_information: SamsungPayMerchantPaymentInformation, + /// Field containing the payment amount + pub amount: SamsungPayAmountDetails, + /// Payment protocol type + pub protocol: SamsungPayProtocolType, + /// List of supported card brands + pub allowed_brands: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SamsungPayProtocolType { + Protocol3ds, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct SamsungPayMerchantPaymentInformation { + /// Merchant name, this will be displayed on the Samsung Pay screen + pub name: String, + /// Merchant domain that process payments + pub url: String, + /// Merchant country code + #[schema(value_type = CountryAlpha2, example = "US")] + pub country_code: api_enums::CountryAlpha2, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct SamsungPayAmountDetails { + #[serde(rename = "option")] + /// Amount format to be displayed + pub amount_format: SamsungPayAmountFormat, + /// The currency code + #[schema(value_type = Currency, example = "USD")] + pub currency_code: api_enums::Currency, + /// The total amount of the transaction + #[serde(rename = "total")] + #[schema(value_type = String, example = "38.02")] + pub total_amount: StringMajorUnit, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SamsungPayAmountFormat { + /// Display the total amount only + FormatTotalPriceOnly, + /// Display "Total (Estimated amount)" and total amount + FormatTotalEstimatedAmount, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub struct GpayShippingAddressParameters { diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index fe39da67a9..68ade2909b 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -56,7 +56,7 @@ impl DashboardRequestPayload { | (Connector::Stripe, WeChatPay) => { Some(api_models::enums::PaymentExperience::DisplayQrCode) } - (_, GooglePay) | (_, ApplePay) => { + (_, GooglePay) | (_, ApplePay) | (_, PaymentMethodType::SamsungPay) => { Some(api_models::enums::PaymentExperience::InvokeSdkClient) } _ => Some(api_models::enums::PaymentExperience::RedirectToUrl), diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index b21bedd880..9f505f2671 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -135,10 +135,30 @@ pub struct MifinityData { } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - +#[serde(rename_all = "snake_case")] pub struct SamsungPayWalletData { - /// The encrypted payment token from Samsung - pub token: Secret, + pub payment_credential: SamsungPayWalletCredentials, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct SamsungPayWalletCredentials { + pub method: Option, + pub recurring_payment: Option, + pub card_brand: String, + #[serde(rename = "card_last4digits")] + pub card_last_four_digits: String, + #[serde(rename = "3_d_s")] + pub token_data: SamsungPayTokenData, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct SamsungPayTokenData { + #[serde(rename = "type")] + pub three_ds_type: Option, + pub version: String, + pub data: Secret, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -670,9 +690,7 @@ impl From for WalletData { }) } api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { - Self::SamsungPay(Box::new(SamsungPayWalletData { - token: samsung_pay_data.token, - })) + Self::SamsungPay(Box::new(SamsungPayWalletData::from(samsung_pay_data))) } api_models::payments::WalletData::TwintRedirect {} => Self::TwintRedirect {}, api_models::payments::WalletData::VippsRedirect {} => Self::VippsRedirect {}, @@ -736,6 +754,24 @@ impl From for ApplePayWalletData { } } +impl From> for SamsungPayWalletData { + fn from(value: Box) -> Self { + Self { + payment_credential: SamsungPayWalletCredentials { + method: value.payment_credential.method, + recurring_payment: value.payment_credential.recurring_payment, + card_brand: value.payment_credential.card_brand, + card_last_four_digits: value.payment_credential.card_last_four_digits, + token_data: SamsungPayTokenData { + three_ds_type: value.payment_credential.token_data.three_ds_type, + version: value.payment_credential.token_data.version, + data: value.payment_credential.token_data.data, + }, + }, + } + } +} + impl From for PayLaterData { fn from(value: api_models::payments::PayLaterData) -> Self { match value { diff --git a/crates/hyperswitch_interfaces/src/errors.rs b/crates/hyperswitch_interfaces/src/errors.rs index 06f197ebf0..c600955734 100644 --- a/crates/hyperswitch_interfaces/src/errors.rs +++ b/crates/hyperswitch_interfaces/src/errors.rs @@ -41,6 +41,8 @@ pub enum ConnectorError { FailedToObtainCertificate, #[error("Connector meta data not found")] NoConnectorMetaData, + #[error("Connector wallet details not found")] + NoConnectorWalletDetails, #[error("Failed to obtain certificate key")] FailedToObtainCertificateKey, #[error("This step has not been implemented for: {0}")] diff --git a/crates/kgraph_utils/benches/evaluation.rs b/crates/kgraph_utils/benches/evaluation.rs index b98b854fba..858c013f41 100644 --- a/crates/kgraph_utils/benches/evaluation.rs +++ b/crates/kgraph_utils/benches/evaluation.rs @@ -71,6 +71,7 @@ fn build_test_data( pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, + connector_wallets_details: None, }; #[cfg(feature = "v1")] @@ -95,6 +96,7 @@ fn build_test_data( pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, + connector_wallets_details: None, }; let config = CountryCurrencyFilter { connector_configs: HashMap::new(), diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 7d04f5d423..2ec0cbe803 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -759,6 +759,7 @@ mod tests { pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, + connector_wallets_details: None, }; #[cfg(feature = "v1")] let stripe_account = MerchantConnectorResponse { @@ -818,6 +819,7 @@ mod tests { pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, additional_merchant_data: None, + connector_wallets_details: None, }; let config_map = kgraph_types::CountryCurrencyFilter { diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index f4cceec8be..8586558449 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -273,6 +273,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::UIWidgetFormLayout, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, + api_models::admin::ConnectorWalletDetails, api_models::admin::MerchantRecipientData, api_models::admin::MerchantAccountData, api_models::admin::MerchantConnectorUpdate, @@ -410,6 +411,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GpayTokenizationData, api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, + api_models::payments::SamsungPayWalletCredentials, + api_models::payments::SamsungPayTokenData, api_models::payments::ApplepayPaymentMethod, api_models::payments::PaymentsCancelRequest, api_models::payments::PaymentListConstraints, @@ -431,6 +434,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayRedirectData, api_models::payments::GooglePayThirdPartySdk, api_models::payments::GooglePaySessionResponse, + api_models::payments::SamsungPaySessionTokenResponse, + api_models::payments::SamsungPayMerchantPaymentInformation, + api_models::payments::SamsungPayAmountDetails, + api_models::payments::SamsungPayAmountFormat, + api_models::payments::SamsungPayProtocolType, api_models::payments::GpayShippingAddressParameters, api_models::payments::GpayBillingAddressParameters, api_models::payments::GpayBillingAddressFormat, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index da0a95aa6d..05fded520f 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -199,6 +199,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::UIWidgetFormLayout, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, + api_models::admin::ConnectorWalletDetails, api_models::admin::MerchantRecipientData, api_models::admin::MerchantAccountData, api_models::admin::MerchantConnectorUpdate, @@ -337,6 +338,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, api_models::payments::ApplepayPaymentMethod, + api_models::payments::SamsungPaySessionTokenResponse, + api_models::payments::SamsungPayMerchantPaymentInformation, + api_models::payments::SamsungPayAmountDetails, + api_models::payments::SamsungPayAmountFormat, + api_models::payments::SamsungPayProtocolType, + api_models::payments::SamsungPayWalletCredentials, + api_models::payments::SamsungPayTokenData, api_models::payments::PaymentsCancelRequest, api_models::payments::PaymentListConstraints, api_models::payments::PaymentListResponse, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 361ebece29..dfb0963c70 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2083,7 +2083,7 @@ impl<'a> TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> domain::WalletData::SamsungPay(samsung_data) => { let data = SamsungPayPmData { payment_type: PaymentType::Samsungpay, - samsung_pay_token: samsung_data.token.to_owned(), + samsung_pay_token: samsung_data.payment_credential.token_data.data.to_owned(), }; Ok(AdyenPaymentMethod::SamsungPay(Box::new(data))) } diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 9ad4f0604a..9c596f5575 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -436,12 +436,27 @@ pub struct FluidData { pub const FLUID_DATA_DESCRIPTOR: &str = "RklEPUNPTU1PTi5BUFBMRS5JTkFQUC5QQVlNRU5U"; +pub const FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY: &str = "FID=COMMON.SAMSUNG.INAPP.PAYMENT"; + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct GooglePayPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SamsungPayTokenizedCard { + transaction_type: TransactionType, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SamsungPayPaymentInformation { + fluid_data: FluidData, + tokenized_card: SamsungPayTokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(untagged)] pub enum PaymentInformation { @@ -450,6 +465,7 @@ pub enum PaymentInformation { ApplePay(Box), ApplePayToken(Box), MandatePayment(Box), + SamsungPay(Box), NetworkToken(Box), } @@ -504,12 +520,15 @@ pub struct AdditionalAmount { pub enum PaymentSolution { ApplePay, GooglePay, + SamsungPay, } #[derive(Debug, Serialize)] pub enum TransactionType { #[serde(rename = "1")] ApplePay, + #[serde(rename = "1")] + SamsungPay, } impl From for String { @@ -517,6 +536,7 @@ impl From for String { let payment_solution = match solution { PaymentSolution::ApplePay => "001", PaymentSolution::GooglePay => "012", + PaymentSolution::SamsungPay => "008", }; payment_solution.to_string() } @@ -575,7 +595,7 @@ impl let mut commerce_indicator = solution .as_ref() .map(|pm_solution| match pm_solution { - PaymentSolution::ApplePay => network + PaymentSolution::ApplePay | PaymentSolution::SamsungPay => network .as_ref() .map(|card_network| match card_network.to_lowercase().as_str() { "amex" => "aesk", @@ -1491,6 +1511,66 @@ impl } } +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, samsung_pay_data): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + ), + ) -> Result { + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + + let payment_information = + PaymentInformation::SamsungPay(Box::new(SamsungPayPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE + .encode(samsung_pay_data.payment_credential.token_data.data.peek()), + ), + descriptor: Some( + consts::BASE64_ENGINE.encode(FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY), + ), + }, + tokenized_card: SamsungPayTokenizedCard { + transaction_type: TransactionType::SamsungPay, + }, + })); + + let processing_information = ProcessingInformation::try_from(( + item, + Some(PaymentSolution::SamsungPay), + Some(samsung_pay_data.payment_credential.card_brand), + ))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> for CybersourcePaymentsRequest { @@ -1588,6 +1668,9 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> domain::WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } + domain::WalletData::SamsungPay(samsung_pay_data) => { + Self::try_from((item, samsung_pay_data)) + } domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) @@ -1604,7 +1687,6 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} | domain::WalletData::TouchNGoRedirect(_) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 65bdeb04a4..ad9925a241 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -2129,10 +2129,14 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect } else { None }, - connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details( - state, &key_store, &metadata, - ) - .await?, + connector_wallets_details: + helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates( + state, + &key_store, + &metadata, + &self.connector_wallets_details, + ) + .await?, }) } } @@ -2306,10 +2310,14 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect } else { None }, - connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details( - state, &key_store, &metadata, - ) - .await?, + connector_wallets_details: + helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates( + state, + &key_store, + &metadata, + &self.connector_wallets_details, + ) + .await?, }) } } @@ -2440,7 +2448,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { applepay_verified_domains: None, pm_auth_config: self.pm_auth_config.clone(), status: connector_status, - connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(state, &key_store, &self.metadata).await?, + connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?, additional_merchant_data: if let Some(mcd) = merchant_recipient_data { Some(domain_types::crypto_operation( key_manager_state, @@ -2605,7 +2613,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { applepay_verified_domains: None, pm_auth_config: self.pm_auth_config.clone(), status: connector_status, - connector_wallets_details: helpers::get_encrypted_apple_pay_connector_wallets_details(state, &key_store, &self.metadata).await?, + connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?, test_mode: self.test_mode, business_country: self.business_country, business_label: self.business_label.clone(), diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs index 0dca02bf79..88e1eb8061 100644 --- a/crates/router/src/core/connector_onboarding/paypal.rs +++ b/crates/router/src/core/connector_onboarding/paypal.rs @@ -162,6 +162,7 @@ pub async fn update_mca( pm_auth_config: None, test_mode: None, additional_merchant_data: None, + connector_wallets_details: None, }; #[cfg(feature = "v2")] let request = MerchantConnectorUpdate { @@ -177,6 +178,7 @@ pub async fn update_mca( pm_auth_config: None, merchant_id: merchant_id.clone(), additional_merchant_data: None, + connector_wallets_details: None, }; let mca_response = admin::update_connector(state.clone(), &merchant_id, None, &connector_id, request).await?; diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index 549857494f..70eaa04cac 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -185,6 +185,7 @@ impl ConnectorErrorExt for error_stack::Result | errors::ConnectorError::FailedToObtainAuthType | errors::ConnectorError::FailedToObtainCertificate | errors::ConnectorError::NoConnectorMetaData + | errors::ConnectorError::NoConnectorWalletDetails | errors::ConnectorError::FailedToObtainCertificateKey | errors::ConnectorError::FlowNotSupported { .. } | errors::ConnectorError::CaptureMethodNotSupported @@ -285,7 +286,7 @@ impl ConnectorErrorExt for error_stack::Result errors::ConnectorError::InvalidWallet | errors::ConnectorError::ResponseHandlingFailed | errors::ConnectorError::FailedToObtainCertificate | - errors::ConnectorError::NoConnectorMetaData | + errors::ConnectorError::NoConnectorMetaData | errors::ConnectorError::NoConnectorWalletDetails | errors::ConnectorError::FailedToObtainCertificateKey | errors::ConnectorError::CaptureMethodNotSupported | errors::ConnectorError::MissingConnectorMandateID | @@ -372,6 +373,7 @@ impl ConnectorErrorExt for error_stack::Result | errors::ConnectorError::FailedToObtainAuthType | errors::ConnectorError::FailedToObtainCertificate | errors::ConnectorError::NoConnectorMetaData + | errors::ConnectorError::NoConnectorWalletDetails | errors::ConnectorError::FailedToObtainCertificateKey | errors::ConnectorError::NotImplemented(_) | errors::ConnectorError::NotSupported { .. } diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 9dfd448288..b65ee66474 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -496,6 +496,62 @@ async fn create_applepay_session_token( } } +fn create_samsung_pay_session_token( + router_data: &types::PaymentsSessionRouterData, + header_payload: api_models::payments::HeaderPayload, +) -> RouterResult { + let samsung_pay_wallet_details = router_data + .connector_wallets_details + .clone() + .parse_value::("SamsungPaySessionTokenData") + .change_context(errors::ConnectorError::NoConnectorWalletDetails) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_wallets_details".to_string(), + expected_format: "samsung_pay_metadata_format".to_string(), + })?; + + let required_amount_type = StringMajorUnitForConnector; + let samsung_pay_amount = required_amount_type + .convert( + router_data.request.minor_amount, + router_data.request.currency, + ) + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: "Failed to convert amount to string major unit for Samsung Pay".to_string(), + })?; + + let merchant_domain = header_payload + .x_merchant_domain + .get_required_value("samsung pay domain") + .attach_printable("Failed to get domain for samsung pay session call")?; + + Ok(types::PaymentsSessionRouterData { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: payment_types::SessionToken::SamsungPay(Box::new( + payment_types::SamsungPaySessionTokenResponse { + version: "2".to_string(), + service_id: samsung_pay_wallet_details.data.service_id, + order_number: router_data.payment_id.clone(), + merchant_payment_information: + payment_types::SamsungPayMerchantPaymentInformation { + name: samsung_pay_wallet_details.data.merchant_display_name, + url: merchant_domain, + country_code: samsung_pay_wallet_details.data.merchant_business_country, + }, + amount: payment_types::SamsungPayAmountDetails { + amount_format: payment_types::SamsungPayAmountFormat::FormatTotalPriceOnly, + currency_code: router_data.request.currency, + total_amount: samsung_pay_amount, + }, + protocol: payment_types::SamsungPayProtocolType::Protocol3ds, + allowed_brands: samsung_pay_wallet_details.data.allowed_brands, + }, + )), + }), + ..router_data.clone() + }) +} + fn get_session_request_for_simplified_apple_pay( apple_pay_merchant_identifier: String, session_token_data: payment_types::SessionTokenForSimplifiedApplePay, @@ -881,6 +937,9 @@ impl RouterDataSession for types::PaymentsSessionRouterData { api::GetToken::GpayMetadata => { create_gpay_session_token(state, self, connector, business_profile) } + api::GetToken::SamsungPayMetadata => { + create_samsung_pay_session_token(self, header_payload) + } api::GetToken::ApplePayMetadata => { create_applepay_session_token( state, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index eb51239923..b0e2f61d18 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4524,30 +4524,34 @@ pub fn is_apple_pay_simplified_flow( )) } -pub async fn get_encrypted_apple_pay_connector_wallets_details( +// This function will return the encrypted connector wallets details with Apple Pay certificates +// Currently apple pay certifiactes are stored in the metadata which is not encrypted. +// In future we want those certificates to be encrypted and stored in the connector_wallets_details. +// As part of migration fallback this function checks apple pay details are present in connector_wallets_details +// If yes, it will encrypt connector_wallets_details and store it in the database. +// If no, it will check if apple pay details are present in metadata and merge it with connector_wallets_details, encrypt and store it. +pub async fn get_encrypted_connector_wallets_details_with_apple_pay_certificates( state: &SessionState, key_store: &domain::MerchantKeyStore, connector_metadata: &Option>, + connector_wallets_details_optional: &Option, ) -> RouterResult>>> { - let apple_pay_metadata = get_applepay_metadata(connector_metadata.clone()) - .map_err(|error| { - logger::error!( - "Apple pay metadata parsing failed in get_encrypted_apple_pay_connector_wallets_details {:?}", - error - ) - }) - .ok(); + let connector_wallet_details_with_apple_pay_metadata_optional = + get_apple_pay_metadata_if_needed(connector_metadata, connector_wallets_details_optional) + .await?; - let connector_apple_pay_details = apple_pay_metadata - .map(|metadata| { - serde_json::to_value(metadata) + let connector_wallets_details = connector_wallet_details_with_apple_pay_metadata_optional + .map(|details| { + serde_json::to_value(details) .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to serialize apple pay metadata as JSON") + .attach_printable("Failed to serialize Apple Pay metadata as JSON") }) .transpose()? .map(masking::Secret::new); + let key_manager_state: KeyManagerState = state.into(); - let encrypted_connector_apple_pay_details = connector_apple_pay_details + let encrypted_connector_wallets_details = connector_wallets_details + .clone() .async_lift(|wallets_details| async { types::crypto_operation( &key_manager_state, @@ -4562,7 +4566,86 @@ pub async fn get_encrypted_apple_pay_connector_wallets_details( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while encrypting connector wallets details")?; - Ok(encrypted_connector_apple_pay_details) + + Ok(encrypted_connector_wallets_details) +} + +async fn get_apple_pay_metadata_if_needed( + connector_metadata: &Option>, + connector_wallets_details_optional: &Option, +) -> RouterResult> { + if let Some(connector_wallets_details) = connector_wallets_details_optional { + if connector_wallets_details.apple_pay_combined.is_some() + || connector_wallets_details.apple_pay.is_some() + { + return Ok(Some(connector_wallets_details.clone())); + } + // Otherwise, merge Apple Pay metadata + return get_and_merge_apple_pay_metadata( + connector_metadata.clone(), + Some(connector_wallets_details.clone()), + ) + .await; + } + + // If connector_wallets_details_optional is None, attempt to get Apple Pay metadata + get_and_merge_apple_pay_metadata(connector_metadata.clone(), None).await +} + +async fn get_and_merge_apple_pay_metadata( + connector_metadata: Option>, + connector_wallets_details_optional: Option, +) -> RouterResult> { + let apple_pay_metadata_optional = get_applepay_metadata(connector_metadata) + .map_err(|error| { + logger::error!( + "Apple Pay metadata parsing failed in get_encrypted_connector_wallets_details_with_apple_pay_certificates {:?}", + error + ); + }) + .ok(); + + if let Some(apple_pay_metadata) = apple_pay_metadata_optional { + let updated_wallet_details = match apple_pay_metadata { + api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( + apple_pay_combined_metadata, + ) => { + let combined_metadata_json = serde_json::to_value(apple_pay_combined_metadata) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to serialize Apple Pay combined metadata as JSON")?; + + api_models::admin::ConnectorWalletDetails { + apple_pay_combined: Some(masking::Secret::new(combined_metadata_json)), + apple_pay: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.apple_pay.clone()), + samsung_pay: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.samsung_pay.clone()), + } + } + api_models::payments::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => { + let metadata_json = serde_json::to_value(apple_pay_metadata) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to serialize Apple Pay metadata as JSON")?; + + api_models::admin::ConnectorWalletDetails { + apple_pay: Some(masking::Secret::new(metadata_json)), + apple_pay_combined: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.apple_pay_combined.clone()), + samsung_pay: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.samsung_pay.clone()), + } + } + }; + + return Ok(Some(updated_wallet_details)); + } + + // Return connector_wallets_details if no Apple Pay metadata was found + Ok(connector_wallets_details_optional) } pub fn get_applepay_metadata( diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 0542c279a2..cc8b839cc8 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -490,6 +490,7 @@ impl From for api::GetToken { match value { api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata, api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata, + api_models::enums::PaymentMethodType::SamsungPay => Self::SamsungPayMetadata, api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata, _ => Self::Connector, } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 34dea537b2..dd5e47b100 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -190,6 +190,7 @@ pub type BoxedConnectorV2 = Box<&'static (dyn ConnectorV2 + Sync)>; #[derive(Clone, Eq, PartialEq, Debug)] pub enum GetToken { GpayMetadata, + SamsungPayMetadata, ApplePayMetadata, PaypalSdkMetadata, Connector, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index e9a70cda9c..bfad15e5d9 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1068,6 +1068,18 @@ impl ForeignTryFrom }) .transpose()? .map(api_models::admin::AdditionalMerchantData::foreign_from), + connector_wallets_details: item + .connector_wallets_details + .map(|data| { + data.into_inner() + .expose() + .parse_value::( + "ConnectorWalletDetails", + ) + .attach_printable("Unable to deserialize connector_wallets_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, }; #[cfg(feature = "v2")] let response = Self { @@ -1096,6 +1108,18 @@ impl ForeignTryFrom }) .transpose()? .map(api_models::admin::AdditionalMerchantData::foreign_from), + connector_wallets_details: item + .connector_wallets_details + .map(|data| { + data.into_inner() + .expose() + .parse_value::( + "ConnectorWalletDetails", + ) + .attach_printable("Unable to deserialize connector_wallets_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, }; Ok(response) } @@ -1191,6 +1215,18 @@ impl ForeignTryFrom }) .transpose()? .map(api_models::admin::AdditionalMerchantData::foreign_from), + connector_wallets_details: item + .connector_wallets_details + .map(|data| { + data.into_inner() + .expose() + .parse_value::( + "ConnectorWalletDetails", + ) + .attach_printable("Unable to deserialize connector_wallets_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, }; #[cfg(feature = "v1")] let response = Self { @@ -1235,6 +1271,18 @@ impl ForeignTryFrom }) .transpose()? .map(api_models::admin::AdditionalMerchantData::foreign_from), + connector_wallets_details: item + .connector_wallets_details + .map(|data| { + data.into_inner() + .expose() + .parse_value::( + "ConnectorWalletDetails", + ) + .attach_printable("Unable to deserialize connector_wallets_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, }; Ok(response) }