diff --git a/Cargo.lock b/Cargo.lock index d9999a69ad..94d137885d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2767,7 +2767,7 @@ dependencies = [ [[package]] name = "opentelemetry" version = "0.18.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" +source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" dependencies = [ "opentelemetry_api", "opentelemetry_sdk", @@ -2776,7 +2776,7 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" version = "0.11.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" +source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" dependencies = [ "async-trait", "futures", @@ -2793,7 +2793,7 @@ dependencies = [ [[package]] name = "opentelemetry-proto" version = "0.1.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" +source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" dependencies = [ "futures", "futures-util", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "opentelemetry_api" version = "0.18.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" +source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" dependencies = [ "fnv", "futures-channel", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" version = "0.18.0" -source = "git+https://github.com/open-telemetry/opentelemetry-rust/?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" +source = "git+https://github.com/open-telemetry/opentelemetry-rust?rev=44b90202fd744598db8b0ace5b8f0bad7ec45658#44b90202fd744598db8b0ace5b8f0bad7ec45658" dependencies = [ "async-trait", "crossbeam-channel", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 0aae07bb04..f7985c1fc4 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -668,16 +668,6 @@ pub enum RoutableConnectors { Worldpay, } -/// Wallets which support obtaining session object -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum SupportedWallets { - Paypal, - ApplePay, - Klarna, - Gpay, -} - /// Name of banks supported by Hyperswitch #[derive( Clone, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index cba73e3f3a..d0165aa1f1 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1339,8 +1339,8 @@ pub struct PaymentsSessionRequest { /// This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK pub client_secret: String, /// The list of the supported wallets - #[schema(value_type = Vec)] - pub wallets: Vec, + #[schema(value_type = Vec)] + pub wallets: Vec, /// Merchant connector details used to make payments. pub merchant_connector_details: Option, } @@ -1412,6 +1412,44 @@ pub struct GpaySessionTokenData { pub data: GpayMetaData, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplepaySessionRequest { + pub merchant_identifier: String, + pub display_name: String, + pub initiative: String, + pub initiative_context: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ApplepaySessionTokenData { + #[serde(rename = "apple_pay")] + pub data: ApplePayMetadata, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ApplePayMetadata { + pub payment_request_data: PaymentRequestMetadata, + pub session_token_data: SessionTokenInfo, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentRequestMetadata { + pub supported_networks: Vec, + pub merchant_capabilities: Vec, + pub label: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SessionTokenInfo { + pub certificate: String, + pub certificate_keys: String, + pub merchant_identifier: String, + pub display_name: String, + pub initiative: String, + pub initiative_context: String, +} + #[derive(Debug, Clone, serde::Serialize, ToSchema)] #[serde(tag = "wallet_name")] #[serde(rename_all = "snake_case")] @@ -1435,6 +1473,7 @@ pub struct GpaySessionTokenResponse { pub allowed_payment_methods: Vec, /// The transaction info Google Pay requires pub transaction_info: GpayTransactionInfo, + pub connector: String, } #[derive(Debug, Clone, serde::Serialize, ToSchema)] @@ -1460,9 +1499,11 @@ pub struct ApplepaySessionTokenResponse { pub session_token_data: ApplePaySessionResponse, /// Payment request object for Apple Pay pub payment_request_data: ApplePayPaymentRequest, + pub connector: String, } #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ApplePaySessionResponse { /// Timestamp at which session is requested pub epoch_timestamp: u64, @@ -1501,6 +1542,7 @@ pub struct ApplePayPaymentRequest { pub merchant_capabilities: Vec, /// The list of supported networks pub supported_networks: Vec, + pub merchant_identifier: String, } #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] @@ -1514,6 +1556,13 @@ pub struct AmountInfo { pub amount: String, } +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplepayErrorResponse { + pub status_code: String, + pub status_message: String, +} + #[derive(Default, Debug, serde::Serialize, Clone, ToSchema)] pub struct PaymentsSessionResponse { /// The identifier for the payment diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index caa22ac115..659dade2f8 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -1,7 +1,6 @@ pub mod aci; pub mod adyen; pub mod airwallex; -pub mod applepay; pub mod authorizedotnet; pub mod bambora; pub mod bluesnap; @@ -32,11 +31,11 @@ pub mod worldpay; pub mod mollie; pub use self::{ - aci::Aci, adyen::Adyen, airwallex::Airwallex, applepay::Applepay, - authorizedotnet::Authorizedotnet, bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree, - checkout::Checkout, coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal, - fiserv::Fiserv, forte::Forte, globalpay::Globalpay, klarna::Klarna, mollie::Mollie, - multisafepay::Multisafepay, nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode, - payeezy::Payeezy, paypal::Paypal, payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe, - trustpay::Trustpay, worldline::Worldline, worldpay::Worldpay, + aci::Aci, adyen::Adyen, airwallex::Airwallex, authorizedotnet::Authorizedotnet, + bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree, checkout::Checkout, + coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv, forte::Forte, + globalpay::Globalpay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, + nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode, payeezy::Payeezy, paypal::Paypal, + payu::Payu, rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay, + worldline::Worldline, worldpay::Worldpay, }; diff --git a/crates/router/src/connector/applepay.rs b/crates/router/src/connector/applepay.rs deleted file mode 100644 index 8bc5ccd5bb..0000000000 --- a/crates/router/src/connector/applepay.rs +++ /dev/null @@ -1,273 +0,0 @@ -mod transformers; - -use std::fmt::Debug; - -use common_utils::ext_traits::ValueExt; -use error_stack::{IntoReport, ResultExt}; - -use self::transformers as applepay; -use crate::{ - configs::settings, - core::errors::{self, CustomResult}, - headers, services, - types::{ - self, - api::{self, ConnectorCommon}, - }, - utils::{self, BytesExt, OptionExt}, -}; - -#[derive(Debug, Clone)] -pub struct Applepay; - -impl ConnectorCommon for Applepay { - fn id(&self) -> &'static str { - "applepay" - } - - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { - connectors.applepay.base_url.as_ref() - } -} - -impl api::Payment for Applepay {} -impl api::PaymentAuthorize for Applepay {} -impl api::PaymentSync for Applepay {} -impl api::PaymentVoid for Applepay {} -impl api::PaymentCapture for Applepay {} -impl api::PreVerify for Applepay {} -impl api::PaymentSession for Applepay {} -impl api::ConnectorAccessToken for Applepay {} -impl api::PaymentToken for Applepay {} - -impl - services::ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Applepay -{ - // Not Implemented (R) -} - -impl - services::ConnectorIntegration< - api::AccessTokenAuth, - types::AccessTokenRequestData, - types::AccessToken, - > for Applepay -{ - // Not Implemented (R) -} - -impl - services::ConnectorIntegration< - api::Verify, - types::VerifyRequestData, - types::PaymentsResponseData, - > for Applepay -{ -} - -impl - services::ConnectorIntegration< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - > for Applepay -{ -} - -impl - services::ConnectorIntegration - for Applepay -{ -} - -impl - services::ConnectorIntegration< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > for Applepay -{ -} - -impl - services::ConnectorIntegration< - api::Void, - types::PaymentsCancelData, - types::PaymentsResponseData, - > for Applepay -{ -} - -#[async_trait::async_trait] -impl - services::ConnectorIntegration< - api::Session, - types::PaymentsSessionData, - types::PaymentsResponseData, - > for Applepay -{ - fn get_headers( - &self, - _req: &types::PaymentsSessionRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let header = vec![( - headers::CONTENT_TYPE.to_string(), - types::PaymentsSessionType::get_content_type(self).to_string(), - )]; - Ok(header) - } - - fn get_url( - &self, - _req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - Ok(format!( - "{}{}", - self.base_url(connectors), - "paymentservices/paymentSession" - )) - } - - fn get_request_body( - &self, - req: &types::PaymentsSessionRouterData, - ) -> CustomResult, errors::ConnectorError> { - let connector_req = applepay::ApplepaySessionRequest::try_from(req)?; - let req = utils::Encode::::encode_to_string_of_json( - &connector_req, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) - } - - fn build_request( - &self, - req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsSessionType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSessionType::get_headers( - self, req, connectors, - )?) - .body(types::PaymentsSessionType::get_request_body(self, req)?) - .add_certificate(types::PaymentsSessionType::get_certificate(self, req)?) - .add_certificate_key(types::PaymentsSessionType::get_certificate_key(self, req)?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &types::PaymentsSessionRouterData, - res: types::Response, - ) -> CustomResult { - let response: applepay::ApplepaySessionTokenResponse = res - .response - .parse_struct("ApplepaySessionResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - types::RouterData::try_from(types::ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) - } - - fn get_error_response( - &self, - res: types::Response, - ) -> CustomResult { - let response: applepay::ErrorResponse = res - .response - .parse_struct("ErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - Ok(types::ErrorResponse { - status_code: res.status_code, - code: response.status_code, - message: response.status_message, - reason: None, - }) - } - - fn get_certificate( - &self, - req: &types::PaymentsSessionRouterData, - ) -> CustomResult, errors::ConnectorError> { - let metadata = req - .connector_meta_data - .to_owned() - .get_required_value("connector_meta_data") - .change_context(errors::ConnectorError::NoConnectorMetaData)?; - - let metadata: transformers::ApplePayMetadata = metadata - .parse_value("ApplePayMetaData") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - Ok(Some(metadata.session_token_data.certificate)) - } - - fn get_certificate_key( - &self, - req: &types::PaymentsSessionRouterData, - ) -> CustomResult, errors::ConnectorError> { - let metadata = req - .connector_meta_data - .to_owned() - .get_required_value("connector_meta_data") - .change_context(errors::ConnectorError::NoConnectorMetaData)?; - - let metadata: transformers::ApplePayMetadata = metadata - .parse_value("ApplePayMetaData") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - Ok(Some(metadata.session_token_data.certificate_keys)) - } -} - -impl api::Refund for Applepay {} -impl api::RefundExecute for Applepay {} -impl api::RefundSync for Applepay {} - -impl services::ConnectorIntegration - for Applepay -{ -} - -impl services::ConnectorIntegration - for Applepay -{ -} - -#[async_trait::async_trait] -impl api::IncomingWebhook for Applepay { - fn get_webhook_object_reference_id( - &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() - } - - fn get_webhook_event_type( - &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() - } - - fn get_webhook_resource_object( - &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(errors::ConnectorError::WebhooksNotImplemented).into_report() - } -} diff --git a/crates/router/src/connector/applepay/transformers.rs b/crates/router/src/connector/applepay/transformers.rs deleted file mode 100644 index 4d20387099..0000000000 --- a/crates/router/src/connector/applepay/transformers.rs +++ /dev/null @@ -1,228 +0,0 @@ -use api_models::payments; -use common_utils::ext_traits::ValueExt; -use error_stack::ResultExt; -use masking::{Deserialize, Serialize}; - -use crate::{connector::utils, core::errors, types, utils::OptionExt}; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ApplepaySessionRequest { - merchant_identifier: String, - display_name: String, - initiative: String, - initiative_context: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApplepaySessionTokenResponse { - pub epoch_timestamp: u64, - pub expires_at: u64, - pub merchant_session_identifier: String, - pub nonce: String, - pub merchant_identifier: String, - pub domain_name: String, - pub display_name: String, - pub signature: String, - pub operational_analytics_identifier: String, - pub retries: u8, - pub psp_id: String, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ErrorResponse { - pub status_code: String, - pub status_message: String, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct ApplePayMetadata { - pub payment_request_data: PaymentRequestMetadata, - pub session_token_data: SessionRequest, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct PaymentRequestMetadata { - pub supported_networks: Vec, - pub merchant_capabilities: Vec, - pub label: String, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct SessionRequest { - pub certificate: String, - pub certificate_keys: String, - pub merchant_identifier: String, - pub display_name: String, - pub initiative: String, - pub initiative_context: String, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct PaymentRequest { - pub apple_pay_merchant_id: String, - pub country_code: api_models::enums::CountryCode, - pub currency_code: String, - pub total: AmountInfo, - pub merchant_capabilities: Vec, - pub supported_networks: Vec, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct AmountInfo { - pub label: String, - #[serde(rename = "type")] - pub label_type: String, - pub amount: String, -} - -impl TryFrom<&types::PaymentsSessionRouterData> for ApplepaySessionRequest { - type Error = error_stack::Report; - fn try_from(item: &types::PaymentsSessionRouterData) -> Result { - let metadata = item - .connector_meta_data - .to_owned() - .get_required_value("connector_meta_data") - .change_context(errors::ConnectorError::NoConnectorMetaData)?; - - let metadata: ApplePayMetadata = metadata - .parse_value("ApplePayMetadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - Ok(Self { - merchant_identifier: metadata.session_token_data.merchant_identifier, - display_name: metadata.session_token_data.display_name, - initiative: metadata.session_token_data.initiative, - initiative_context: metadata.session_token_data.initiative_context, - }) - } -} - -impl - TryFrom< - types::ResponseRouterData< - F, - ApplepaySessionTokenResponse, - types::PaymentsSessionData, - types::PaymentsResponseData, - >, - > for types::RouterData -{ - type Error = error_stack::Report; - fn try_from( - item: types::ResponseRouterData< - F, - ApplepaySessionTokenResponse, - types::PaymentsSessionData, - types::PaymentsResponseData, - >, - ) -> Result { - let metadata = item - .data - .connector_meta_data - .to_owned() - .get_required_value("connector_meta_data") - .change_context(errors::ConnectorError::NoConnectorMetaData)?; - - let metadata: ApplePayMetadata = metadata - .parse_value("ApplePayMetadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - - let amount_info = AmountInfo { - label: metadata.payment_request_data.label, - label_type: "final".to_string(), - amount: utils::to_currency_base_unit( - item.data.request.amount, - item.data.request.currency, - )?, - }; - - let payment_request = PaymentRequest { - country_code: item - .data - .request - .country - .to_owned() - .get_required_value("country_code") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "country_code", - })?, - currency_code: item.data.request.currency.to_string(), - total: amount_info, - merchant_capabilities: metadata.payment_request_data.merchant_capabilities, - supported_networks: metadata.payment_request_data.supported_networks, - apple_pay_merchant_id: metadata.session_token_data.merchant_identifier, - }; - - let applepay_session = ApplepaySessionTokenResponse { - epoch_timestamp: item.response.epoch_timestamp, - expires_at: item.response.expires_at, - merchant_session_identifier: item.response.merchant_session_identifier, - nonce: item.response.nonce, - merchant_identifier: item.response.merchant_identifier, - domain_name: item.response.domain_name, - display_name: item.response.display_name, - signature: item.response.signature, - operational_analytics_identifier: item.response.operational_analytics_identifier, - retries: item.response.retries, - psp_id: item.response.psp_id, - }; - - Ok(Self { - response: Ok(types::PaymentsResponseData::SessionResponse { - session_token: { - api_models::payments::SessionToken::ApplePay(Box::new( - payments::ApplepaySessionTokenResponse { - session_token_data: applepay_session.into(), - payment_request_data: payment_request.into(), - }, - )) - }, - }), - ..item.data - }) - } -} - -impl From for payments::ApplePayPaymentRequest { - fn from(value: PaymentRequest) -> Self { - Self { - country_code: value.country_code, - currency_code: value.currency_code, - total: value.total.into(), - merchant_capabilities: value.merchant_capabilities, - supported_networks: value.supported_networks, - } - } -} - -impl From for payments::AmountInfo { - fn from(value: AmountInfo) -> Self { - Self { - label: value.label, - total_type: value.label_type, - amount: value.amount, - } - } -} - -impl From for payments::ApplePaySessionResponse { - fn from(value: ApplepaySessionTokenResponse) -> Self { - Self { - epoch_timestamp: value.epoch_timestamp, - expires_at: value.expires_at, - merchant_session_identifier: value.merchant_session_identifier, - nonce: value.nonce, - merchant_identifier: value.merchant_identifier, - domain_name: value.domain_name, - display_name: value.display_name, - signature: value.signature, - operational_analytics_identifier: value.operational_analytics_identifier, - retries: value.retries, - psp_id: value.psp_id, - } - } -} diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index ff0a3c84d8..2c1a0a1c44 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -88,7 +88,6 @@ macro_rules! default_imp_for_complete_authorize{ default_imp_for_complete_authorize!( connector::Aci, connector::Adyen, - connector::Applepay, connector::Authorizedotnet, connector::Bambora, connector::Bluesnap, @@ -132,7 +131,6 @@ macro_rules! default_imp_for_connector_redirect_response{ default_imp_for_connector_redirect_response!( connector::Aci, connector::Adyen, - connector::Applepay, connector::Authorizedotnet, connector::Bambora, connector::Bluesnap, @@ -166,7 +164,6 @@ default_imp_for_connector_request_id!( connector::Aci, connector::Adyen, connector::Airwallex, - connector::Applepay, connector::Authorizedotnet, connector::Bambora, connector::Bluesnap, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 78360ce080..53abde90bd 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -1,17 +1,20 @@ use api_models::payments as payment_types; use async_trait::async_trait; -use error_stack::ResultExt; +use common_utils::ext_traits::ByteSliceExt; +use error_stack::{report, ResultExt}; use super::{ConstructFlowSpecificData, Feature}; use crate::{ + connector, core::{ errors::{self, ConnectorErrorExt, RouterResult}, payments::{self, access_token, transformers, PaymentData}, }, + headers, routes::{self, metrics}, services, types::{self, api, storage}, - utils::OptionExt, + utils::{self, OptionExt}, }; #[async_trait] @@ -73,8 +76,159 @@ impl Feature for types::PaymentsSessio } } +fn mk_applepay_session_request( + state: &routes::AppState, + router_data: &types::PaymentsSessionRouterData, +) -> RouterResult<(services::Request, payment_types::ApplepaySessionTokenData)> { + let connector_metadata = router_data.connector_meta_data.clone(); + + let applepay_metadata = connector_metadata + .parse_value::("ApplepaySessionTokenData") + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_metadata".to_string(), + expected_format: "applepay_metadata_format".to_string(), + })?; + let request = payment_types::ApplepaySessionRequest { + merchant_identifier: applepay_metadata + .data + .session_token_data + .merchant_identifier + .clone(), + display_name: applepay_metadata + .data + .session_token_data + .display_name + .clone(), + initiative: applepay_metadata.data.session_token_data.initiative.clone(), + initiative_context: applepay_metadata + .data + .session_token_data + .initiative_context + .clone(), + }; + + let applepay_session_request = + utils::Encode::::encode_to_string_of_json(&request) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode ApplePay session request to a string of json")?; + + let mut url = state.conf.connectors.applepay.base_url.to_owned(); + url.push_str("paymentservices/paymentSession"); + + let session_request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(url.as_str()) + .attach_default_headers() + .headers(vec![( + headers::CONTENT_TYPE.to_string(), + "application/json".to_string(), + )]) + .body(Some(applepay_session_request)) + .add_certificate(Some( + applepay_metadata + .data + .session_token_data + .certificate + .clone(), + )) + .add_certificate_key(Some( + applepay_metadata + .data + .session_token_data + .certificate_keys + .clone(), + )) + .build(); + Ok((session_request, applepay_metadata)) +} + +async fn create_applepay_session_token( + state: &routes::AppState, + router_data: &types::PaymentsSessionRouterData, + connector: &api::ConnectorData, +) -> RouterResult { + let (applepay_session_request, applepay_metadata) = + mk_applepay_session_request(state, router_data)?; + let response = services::call_connector_api(state, applepay_session_request) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failure in calling connector api")?; + let session_response: payment_types::ApplePaySessionResponse = match response { + Ok(resp) => resp + .response + .parse_struct("ApplePaySessionResponse") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse ApplePaySessionResponse struct"), + Err(err) => { + let error_response: payment_types::ApplepayErrorResponse = err + .response + .parse_struct("ApplepayErrorResponse") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse ApplepayErrorResponse struct")?; + Err( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable(format!( + "Failed with {} status code and the error response is {:?}", + err.status_code, error_response + )), + ) + } + }?; + + let amount_info = payment_types::AmountInfo { + label: applepay_metadata.data.payment_request_data.label, + total_type: "final".to_string(), + amount: connector::utils::to_currency_base_unit( + router_data.request.amount, + router_data.request.currency, + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert currency to base unit")?, + }; + + let applepay_payment_request = payment_types::ApplePayPaymentRequest { + country_code: router_data + .request + .country + .to_owned() + .get_required_value("country_code") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "country_code", + })?, + currency_code: router_data.request.currency.to_string(), + total: amount_info, + merchant_capabilities: applepay_metadata + .data + .payment_request_data + .merchant_capabilities, + supported_networks: applepay_metadata + .data + .payment_request_data + .supported_networks, + merchant_identifier: applepay_metadata + .data + .session_token_data + .merchant_identifier, + }; + + let response_router_data = types::PaymentsSessionRouterData { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: payment_types::SessionToken::ApplePay(Box::new( + payment_types::ApplepaySessionTokenResponse { + session_token_data: session_response, + payment_request_data: applepay_payment_request, + connector: connector.connector_name.to_string(), + }, + )), + }), + ..router_data.clone() + }; + + Ok(response_router_data) +} + fn create_gpay_session_token( router_data: &types::PaymentsSessionRouterData, + connector: &api::ConnectorData, ) -> RouterResult { let connector_metadata = router_data.connector_meta_data.clone(); @@ -105,6 +259,7 @@ fn create_gpay_session_token( merchant_info: gpay_data.data.merchant_info, allowed_payment_methods: gpay_data.data.allowed_payment_methods, transaction_info, + connector: connector.connector_name.to_string(), }, )), }), @@ -124,7 +279,10 @@ impl types::PaymentsSessionRouterData { call_connector_action: payments::CallConnectorAction, ) -> RouterResult { match connector.get_token { - api::GetToken::Metadata => create_gpay_session_token(self), + api::GetToken::GpayMetadata => create_gpay_session_token(self, connector), + api::GetToken::ApplePayMetadata => { + create_applepay_session_token(state, self, connector).await + } api::GetToken::Connector => { let connector_integration: services::BoxedConnectorIntegration< '_, diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 008e0efd46..27b819ca1f 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, marker::PhantomData}; +use std::marker::PhantomData; use api_models::admin::PaymentMethodsEnabled; use async_trait::async_trait; @@ -18,7 +18,7 @@ use crate::{ pii::Secret, routes::AppState, types::{ - api::{self, enums as api_enums, PaymentIdTypeExt}, + api::{self, PaymentIdTypeExt}, storage::{self, enums as storage_enums}, transformers::ForeignInto, }, @@ -293,8 +293,6 @@ where let connectors = &state.conf.connectors; let db = &state.store; - let supported_connectors: &Vec = state.conf.connectors.supported.wallets.as_ref(); - let connector_accounts = db .find_merchant_connector_account_by_merchant_id_and_disabled_list( &merchant_account.merchant_id, @@ -304,127 +302,73 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Database error when querying for merchant connector accounts")?; - let normal_connector_names: HashSet = connector_accounts - .iter() - .filter(|connector_account| { - connector_account - .payment_methods_enabled - .clone() - .unwrap_or_default() - .iter() - .any(|payment_method| { - let parsed_payment_method_result: Result< - PaymentMethodsEnabled, - error_stack::Report, - > = payment_method.clone().parse_value("payment_method"); + let mut connector_and_supporting_payment_method_type = Vec::new(); - match parsed_payment_method_result { - Ok(parsed_payment_method) => parsed_payment_method - .payment_method_types - .map(|payment_method_types| { - payment_method_types.iter().any(|payment_method_type| { - matches!( - payment_method_type.payment_experience, - Some(api_models::enums::PaymentExperience::InvokeSdkClient) - ) - }) - }) - .unwrap_or(false), - Err(parsing_error) => { - logger::debug!(session_token_parsing_error=?parsing_error); - false + for connector_account in connector_accounts { + let payment_methods = connector_account + .payment_methods_enabled + .unwrap_or_default(); + for payment_method in payment_methods { + let parsed_payment_method_result: Result< + PaymentMethodsEnabled, + error_stack::Report, + > = payment_method.clone().parse_value("payment_method"); + + match parsed_payment_method_result { + Ok(parsed_payment_method) => { + let payment_method_types = parsed_payment_method + .payment_method_types + .unwrap_or_default(); + for payment_method_type in payment_method_types { + if matches!( + payment_method_type.payment_experience, + Some(api_models::enums::PaymentExperience::InvokeSdkClient) + ) { + let connector_and_wallet = ( + connector_account.connector_name.to_owned(), + payment_method_type.payment_method_type, + ); + connector_and_supporting_payment_method_type + .push(connector_and_wallet); } } - }) - }) - .map(|filtered_connector| filtered_connector.connector_name.clone()) - .collect(); + } + Err(parsing_error) => { + logger::debug!(session_token_parsing_error=?parsing_error); + } + } + } + } - // Parse the payment methods enabled to check if the merchant has enabled googlepay ( wallet ) using that connector. - // A single connector can support creating session token from metadata as well as by calling the connector. - let session_token_from_metadata_connectors = connector_accounts - .iter() - .filter(|connector_account| { - connector_account - .payment_methods_enabled - .clone() - .unwrap_or_default() - .iter() - .any(|payment_method| { - let parsed_payment_method_result: Result< - PaymentMethodsEnabled, - error_stack::Report, - > = payment_method.clone().parse_value("payment_method"); + let requested_payment_method_types = request.wallets.clone(); - match parsed_payment_method_result { - Ok(parsed_payment_method) => parsed_payment_method - .payment_method_types - .map(|payment_method_types| { - payment_method_types.iter().any(|payment_method_type| { - matches!( - payment_method_type.payment_method_type, - api_models::enums::PaymentMethodType::GooglePay - ) - }) - }) - .unwrap_or(false), - Err(parsing_error) => { - logger::debug!(session_token_parsing_error=?parsing_error); - false - } - } - }) - }) - .map(|filtered_connector| filtered_connector.connector_name.clone()) - .collect::>(); - - let given_wallets = request.wallets.clone(); - - let connectors_data = if !given_wallets.is_empty() { - // Create connectors for provided wallets - let mut connectors_data = Vec::with_capacity(supported_connectors.len()); - for wallet in given_wallets { - let (connector_name, connector_type) = match wallet { - api_enums::SupportedWallets::Gpay => ("adyen", api::GetToken::Metadata), - api_enums::SupportedWallets::ApplePay => ("applepay", api::GetToken::Connector), - api_enums::SupportedWallets::Paypal => ("braintree", api::GetToken::Connector), - api_enums::SupportedWallets::Klarna => ("klarna", api::GetToken::Connector), - }; - - // Check if merchant has enabled the required merchant connector account - if session_token_from_metadata_connectors.contains(connector_name) - || normal_connector_names.contains(connector_name) + let connectors_data = if !requested_payment_method_types.is_empty() { + let mut connectors_data = Vec::new(); + for payment_method_type in requested_payment_method_types { + for connector_and_payment_method_type in + &connector_and_supporting_payment_method_type { - connectors_data.push(api::ConnectorData::get_connector_by_name( - connectors, - connector_name, - connector_type, - )?); + if connector_and_payment_method_type.1 == payment_method_type { + let connector_details = api::ConnectorData::get_connector_by_name( + connectors, + connector_and_payment_method_type.0.as_str(), + api::GetToken::from(connector_and_payment_method_type.1), + )?; + connectors_data.push(connector_details); + } } } connectors_data } else { - // Create connectors for all enabled wallets - let mut connectors_data = Vec::with_capacity( - normal_connector_names.len() + session_token_from_metadata_connectors.len(), - ); + let mut connectors_data = Vec::new(); - for connector_name in normal_connector_names { - let connector_data = api::ConnectorData::get_connector_by_name( + for connector_and_payment_method_type in connector_and_supporting_payment_method_type { + let connector_details = api::ConnectorData::get_connector_by_name( connectors, - &connector_name, - api::GetToken::Connector, + connector_and_payment_method_type.0.as_str(), + api::GetToken::from(connector_and_payment_method_type.1), )?; - connectors_data.push(connector_data); - } - - for connector_name in session_token_from_metadata_connectors { - let connector_data = api::ConnectorData::get_connector_by_name( - connectors, - &connector_name, - api::GetToken::Metadata, - )?; - connectors_data.push(connector_data); + connectors_data.push(connector_details); } connectors_data }; @@ -432,3 +376,13 @@ where Ok(api::ConnectorChoice::SessionMultiple(connectors_data)) } } + +impl From for api::GetToken { + fn from(value: api_models::enums::PaymentMethodType) -> Self { + match value { + api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata, + api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata, + _ => Self::Connector, + } + } +} diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index ec506f3f10..d16e2ef597 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -140,7 +140,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::AuthenticationType, api_models::enums::Connector, api_models::enums::PaymentMethod, - api_models::enums::SupportedWallets, api_models::enums::PaymentMethodIssuerCode, api_models::enums::MandateStatus, api_models::enums::PaymentExperience, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index ca8b5940c8..84304d6f1c 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -131,9 +131,10 @@ type BoxedConnector = Box<&'static (dyn Connector + Sync)>; // Normal flow will call the connector and follow the flow specific operations (capture, authorize) // SessionTokenFromMetadata will avoid calling the connector instead create the session token ( for sdk ) -#[derive(Clone)] +#[derive(Clone, Eq, PartialEq)] pub enum GetToken { - Metadata, + GpayMetadata, + ApplePayMetadata, Connector, } @@ -189,7 +190,6 @@ impl ConnectorData { "aci" => Ok(Box::new(&connector::Aci)), "adyen" => Ok(Box::new(&connector::Adyen)), "airwallex" => Ok(Box::new(&connector::Airwallex)), - "applepay" => Ok(Box::new(&connector::Applepay)), "authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)), "bambora" => Ok(Box::new(&connector::Bambora)), "bluesnap" => Ok(Box::new(&connector::Bluesnap)),