From a6e91a828b4843b442c97f46f9c9e44eeb8bef9f Mon Sep 17 00:00:00 2001 From: Sangamesh Kulkarni <59434228+Sangamesh26@users.noreply.github.com> Date: Wed, 7 Jun 2023 20:27:19 +0530 Subject: [PATCH] feat: Session flow for Apple Pay trustpay (#1155) --- config/development.toml | 3 + crates/api_models/src/payments.rs | 59 ++++++- crates/router/src/configs/settings.rs | 22 +++ .../src/connector/bluesnap/transformers.rs | 39 +++-- .../src/connector/braintree/transformers.rs | 1 + .../src/connector/klarna/transformers.rs | 1 + crates/router/src/connector/trustpay.rs | 81 +++++++++ .../src/connector/trustpay/transformers.rs | 122 ++++++++++++++ crates/router/src/core/payments.rs | 107 ++++++++---- .../src/core/payments/flows/session_flow.rs | 158 ++++++++++++------ .../payments/operations/payment_cancel.rs | 1 + .../payments/operations/payment_capture.rs | 1 + .../operations/payment_complete_authorize.rs | 1 + .../payments/operations/payment_confirm.rs | 1 + .../payments/operations/payment_create.rs | 1 + .../operations/payment_method_validate.rs | 1 + .../payments/operations/payment_session.rs | 39 +++-- .../core/payments/operations/payment_start.rs | 1 + .../payments/operations/payment_status.rs | 1 + .../payments/operations/payment_update.rs | 1 + crates/router/src/openapi.rs | 5 + crates/router/src/types.rs | 1 + crates/storage_models/src/payment_attempt.rs | 9 + 23 files changed, 541 insertions(+), 115 deletions(-) diff --git a/config/development.toml b/config/development.toml index 1921e0791e..67fa9a6cae 100644 --- a/config/development.toml +++ b/config/development.toml @@ -257,3 +257,6 @@ refund_duration = 1000 refund_tolerance = 100 refund_retrieve_duration = 500 refund_retrieve_tolerance = 100 + +[delayed_session_response] +connectors_with_delayed_session_response = "trustpay" diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 8f52619c5d..0b8e778c6c 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1620,6 +1620,8 @@ pub struct PaymentsSessionRequest { /// Merchant connector details used to make payments. #[schema(value_type = Option)] pub merchant_connector_details: Option, + /// Identifier for the delayed session response + pub delayed_session_token: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] @@ -1783,13 +1785,47 @@ pub struct ApplepaySessionTokenResponse { /// Session object for Apple Pay pub session_token_data: ApplePaySessionResponse, /// Payment request object for Apple Pay - pub payment_request_data: ApplePayPaymentRequest, + pub payment_request_data: Option, + /// The session token is w.r.t this connector pub connector: String, + /// Identifier for the delayed session response + pub delayed_session_token: bool, + /// The next action for the sdk (ex: calling confirm or sync call) + pub sdk_next_action: SdkNextAction, +} + +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct SdkNextAction { + /// The type of next action + pub next_action: NextActionCall, +} + +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum NextActionCall { + /// The next action call is confirm + Confirm, + /// The next action call is sync + Sync, + /// The next action call is session + SessionToken, +} + +#[derive(Debug, Clone, serde::Serialize, ToSchema)] +#[serde(untagged)] +pub enum ApplePaySessionResponse { + /// We get this session response, when third party sdk is involved + ThirdPartySdk(ThirdPartySdkSessionResponse), + /// We get this session response, when there is no involvement of third party sdk + /// This is the common response most of the times + NoThirdPartySdk(Option), + /// This is for the empty session response + NoSessionResponse, } #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub struct ApplePaySessionResponse { +pub struct NoThirdPartySdkSessionResponse { /// Timestamp at which session is requested pub epoch_timestamp: u64, /// Timestamp at which session expires @@ -1814,6 +1850,21 @@ pub struct ApplePaySessionResponse { pub psp_id: String, } +#[derive(Debug, Clone, serde::Serialize, ToSchema)] +pub struct ThirdPartySdkSessionResponse { + pub secrets: SecretInfoToInitiateSdk, +} + +#[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] +pub struct SecretInfoToInitiateSdk { + // Authorization secrets used by client to initiate sdk + #[schema(value_type = String)] + pub display: Secret, + // Authorization secrets used by client for payment + #[schema(value_type = String)] + pub payment: Secret, +} + #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] pub struct ApplePayPaymentRequest { /// The code for country @@ -1827,7 +1878,7 @@ pub struct ApplePayPaymentRequest { pub merchant_capabilities: Vec, /// The list of supported networks pub supported_networks: Vec, - pub merchant_identifier: String, + pub merchant_identifier: Option, } #[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] @@ -1836,7 +1887,7 @@ pub struct AmountInfo { pub label: String, /// A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending. #[serde(rename = "type")] - pub total_type: String, + pub total_type: Option, /// The total amount for the payment pub amount: String, } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 4d4f6d66d7..969075e04b 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -83,6 +83,7 @@ pub struct Settings { pub dummy_connector: DummyConnector, #[cfg(feature = "email")] pub email: EmailSettings, + pub delayed_session_response: DelayedSessionConfig, } #[derive(Debug, Deserialize, Clone, Default)] @@ -506,6 +507,27 @@ pub struct FileUploadConfig { pub bucket_name: String, } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct DelayedSessionConfig { + #[serde(deserialize_with = "delayed_session_deser")] + pub connectors_with_delayed_session_response: HashSet, +} + +fn delayed_session_deser<'a, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'a>, +{ + let value = ::deserialize(deserializer)?; + value + .trim() + .split(',') + .map(api_models::enums::Connector::from_str) + .collect::>() + .map_err(D::Error::custom) +} + impl Settings { pub fn new() -> ApplicationResult { Self::with_config_path(None) diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/router/src/connector/bluesnap/transformers.rs index 2069ea7dbf..12d50ae6a8 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/router/src/connector/bluesnap/transformers.rs @@ -1,4 +1,4 @@ -use api_models::enums as api_enums; +use api_models::{enums as api_enums, payments}; use base64::Engine; use common_utils::{ ext_traits::{ByteSliceExt, StringExt, ValueExt}, @@ -286,11 +286,12 @@ impl TryFrom session_token: item.response.client_token.value, }, )), + response_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index 62ec396121..6117289b5d 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -78,6 +78,7 @@ impl TryFrom> session_id: response.session_id.clone(), }, )), + response_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 98fce9b4a3..6cc5be53bb 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -339,6 +339,87 @@ impl api::PaymentSession for Trustpay {} impl ConnectorIntegration for Trustpay { + fn get_headers( + &self, + req: &types::PaymentsSessionRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + types::PaymentsSessionType::get_content_type(self) + .to_string() + .into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsSessionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "api/v1/intent")) + } + + fn get_request_body( + &self, + req: &types::PaymentsSessionRouterData, + ) -> CustomResult, errors::ConnectorError> { + let create_intent_req = trustpay::TrustpayCreateIntentRequest::try_from(req)?; + let trustpay_req = + utils::Encode::::url_encode(&create_intent_req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(trustpay_req)) + } + + fn build_request( + &self, + req: &types::PaymentsSessionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .attach_default_headers() + .headers(types::PaymentsSessionType::get_headers( + self, req, connectors, + )?) + .url(&types::PaymentsSessionType::get_url(self, req, connectors)?) + .body(types::PaymentsSessionType::get_request_body(self, req)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &types::PaymentsSessionRouterData, + res: Response, + ) -> CustomResult { + let response: trustpay::TrustpayCreateIntentResponse = res + .response + .parse_struct("TrustpayCreateIntentResponse") + .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: Response, + ) -> CustomResult { + self.build_error_response(res) + } } impl api::PaymentAuthorize for Trustpay {} diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 35124b5236..92e6e7da35 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -801,6 +801,128 @@ impl TryFrom, +} + +impl TryFrom<&types::PaymentsSessionRouterData> for TrustpayCreateIntentRequest { + type Error = Error; + fn try_from(item: &types::PaymentsSessionRouterData) -> Result { + Ok(Self { + amount: item.request.amount.to_string(), + currency: item.request.currency.to_string(), + init_apple_pay: Some(true), + }) + } +} + +#[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrustpayCreateIntentResponse { + // TrustPay's authorization secrets used by client + pub secrets: SdkSecretInfo, + // Data object to be used for Apple Pay + pub apple_init_result_data: TrustpayApplePayResponse, + // Unique operation/transaction identifier + pub instance_id: String, +} + +#[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SdkSecretInfo { + pub display: Secret, + pub payment: Secret, +} + +#[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrustpayApplePayResponse { + pub country_code: api_models::enums::CountryAlpha2, + pub currency_code: String, + pub supported_networks: Vec, + pub merchant_capabilities: Vec, + pub total: ApplePayTotalInfo, +} + +#[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTotalInfo { + pub label: String, + pub amount: String, +} + +impl TryFrom> + for types::PaymentsSessionRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::PaymentsSessionResponseRouterData, + ) -> Result { + let response = item.response; + Ok(Self { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: types::api::SessionToken::ApplePay(Box::new( + api_models::payments::ApplepaySessionTokenResponse { + session_token_data: + api_models::payments::ApplePaySessionResponse::ThirdPartySdk( + api_models::payments::ThirdPartySdkSessionResponse { + secrets: response.secrets.into(), + }, + ), + payment_request_data: Some(api_models::payments::ApplePayPaymentRequest { + country_code: response.apple_init_result_data.country_code, + currency_code: response.apple_init_result_data.currency_code.clone(), + supported_networks: response + .apple_init_result_data + .supported_networks + .clone(), + merchant_capabilities: response + .apple_init_result_data + .merchant_capabilities + .clone(), + total: response.apple_init_result_data.total.into(), + merchant_identifier: None, + }), + connector: "trustpay".to_string(), + delayed_session_token: true, + sdk_next_action: { + api_models::payments::SdkNextAction { + next_action: api_models::payments::NextActionCall::Sync, + } + }, + }, + )), + response_id: Some(response.instance_id), + }), + ..item.data + }) + } +} + +impl From for api_models::payments::SecretInfoToInitiateSdk { + fn from(value: SdkSecretInfo) -> Self { + Self { + display: value.display, + payment: value.payment, + } + } +} + +impl From for api_models::payments::AmountInfo { + fn from(value: ApplePayTotalInfo) -> Self { + Self { + label: value.label, + amount: value.amount, + total_type: None, + } + } +} + #[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct TrustpayRefundRequestCards { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f2392d2693..a9c6399b34 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -8,10 +8,10 @@ pub mod transformers; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; +use actix_web::ResponseError; use api_models::payments::Metadata; use common_utils::pii::Email; use error_stack::{IntoReport, ResultExt}; -use futures::future::join_all; use masking::Secret; use router_env::{instrument, tracing}; use storage_models::ephemeral_key; @@ -29,11 +29,10 @@ use self::{ use crate::{ configs::settings::PaymentMethodTypeTokenFilter, core::{ - errors::{self, CustomResult, RouterResponse, RouterResult}, + errors::{self, CustomResult, RouterResponse, RouterResult, StorageErrorExt}, payment_methods::vault, }, db::StorageInterface, - logger, routes::{metrics, AppState}, scheduler::utils as pt_utils, services::{self, api::Authenticate}, @@ -177,7 +176,7 @@ where } api::ConnectorCallType::Multiple(connectors) => { - call_multiple_connectors_service( + get_session_tokens_and_persist_if_required( state, &merchant_account, connectors, @@ -564,7 +563,7 @@ where router_data_res } -pub async fn call_multiple_connectors_service( +pub async fn get_session_tokens_and_persist_if_required( state: &AppState, merchant_account: &domain::MerchantAccount, connectors: Vec, @@ -596,36 +595,52 @@ where .construct_router_data(state, connector_id, merchant_account, customer) .await?; - let res = router_data.decide_flows( - state, - &session_connector_data.connector, - customer, - CallConnectorAction::Trigger, - merchant_account, - ); + let res = router_data + .decide_flows( + state, + &session_connector_data.connector, + customer, + CallConnectorAction::Trigger, + merchant_account, + ) + .await; - join_handlers.push(res); + let router_res = match res { + Ok(router_result) => Ok(router_result), + Err(error) => { + let err = error.current_context(); + Err(errors::ApiErrorResponse::ExternalConnectorError { + code: err.error_code(), + message: err.error_message(), + connector: connector_id.to_string(), + status_code: err.status_code().into(), + reason: None, + }) + } + }?; + + join_handlers.push(router_res.response.clone()); + + if connector_id == "trustpay" && payment_data.delayed_session_token.unwrap_or(false) { + //Fix: Add post update tracker for payment_session operation + let response_id = router_res.response.ok().and_then(|res| match res { + types::PaymentsResponseData::SessionResponse { response_id, .. } => response_id, + _ => None, + }); + + update_connector_txn_id_in_payment_attempt( + state, + merchant_account, + &payment_data, + response_id, + ) + .await?; + }; } - let result = join_all(join_handlers).await; - - for (connector_res, session_connector) in result.into_iter().zip(connectors) { - let connector_name = session_connector.connector.connector_name.to_string(); - match connector_res { - Ok(connector_response) => { - if let Ok(types::PaymentsResponseData::SessionResponse { session_token }) = - connector_response.response - { - payment_data.sessions_token.push(session_token); - } - } - Err(connector_error) => { - logger::error!( - "sessions_connector_error {} {:?}", - connector_name, - connector_error - ); - } + for connector_res in join_handlers.into_iter().flatten() { + if let types::PaymentsResponseData::SessionResponse { session_token, .. } = connector_res { + payment_data.sessions_token.push(session_token); } } @@ -637,6 +652,31 @@ where Ok(payment_data) } +pub async fn update_connector_txn_id_in_payment_attempt( + state: &AppState, + merchant_account: &domain::MerchantAccount, + payment_data: &PaymentData, + response_id: Option, +) -> RouterResult<()> +where + F: Clone, +{ + let payment_attempt_update = storage::PaymentAttemptUpdate::SessionUpdate { + connector_transaction_id: response_id, + }; + + let db = &*state.store; + db.update_payment_attempt_with_attempt_id( + payment_data.payment_attempt.to_owned(), + payment_attempt_update, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + Ok(()) +} + pub async fn call_create_connector_customer_if_required( state: &AppState, customer: &Option, @@ -975,6 +1015,7 @@ where pub connector_customer_id: Option, pub ephemeral_key: Option, pub redirect_response: Option, + pub delayed_session_token: Option, } #[derive(Debug, Default)] @@ -1085,7 +1126,7 @@ pub async fn list_payments( ) -> RouterResponse { use futures::stream::StreamExt; - use crate::{core::errors::utils::StorageErrorExt, types::transformers::ForeignFrom}; + use crate::types::transformers::ForeignFrom; helpers::validate_payment_list_request(&constraints)?; let merchant_id = &merchant.merchant_id; diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 1e0c15f360..0e186596e4 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -1,7 +1,7 @@ use api_models::payments as payment_types; use async_trait::async_trait; use common_utils::ext_traits::ByteSliceExt; -use error_stack::{report, ResultExt}; +use error_stack::{Report, ResultExt}; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -10,7 +10,7 @@ use crate::{ errors::{self, ConnectorErrorExt, RouterResult}, payments::{self, access_token, transformers, PaymentData}, }, - headers, + headers, logger, routes::{self, metrics}, services, types::{self, api, domain}, @@ -78,18 +78,22 @@ 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 +fn get_applepay_metadata( + connector_metadata: Option, +) -> RouterResult { + connector_metadata .parse_value::("ApplepaySessionTokenData") .change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "connector_metadata".to_string(), expected_format: "applepay_metadata_format".to_string(), - })?; + }) +} + +fn mk_applepay_session_request( + state: &routes::AppState, + router_data: &types::PaymentsSessionRouterData, +) -> RouterResult { + let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; let request = payment_types::ApplepaySessionRequest { merchant_identifier: applepay_metadata .data @@ -134,14 +138,10 @@ fn mk_applepay_session_request( .clone(), )) .add_certificate_key(Some( - applepay_metadata - .data - .session_token_data - .certificate_keys - .clone(), + applepay_metadata.data.session_token_data.certificate_keys, )) .build(); - Ok((session_request, applepay_metadata)) + Ok(session_request) } async fn create_applepay_session_token( @@ -149,36 +149,17 @@ async fn create_applepay_session_token( 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 connectors_with_delayed_response = &state + .conf + .delayed_session_response + .connectors_with_delayed_session_response; + + let connector_name = connector.connector_name; + let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; let amount_info = payment_types::AmountInfo { label: applepay_metadata.data.payment_request_data.label, - total_type: "final".to_string(), + total_type: Some("final".to_string()), amount: connector::utils::to_currency_base_unit( router_data.request.amount, router_data.request.currency, @@ -206,26 +187,84 @@ async fn create_applepay_session_token( .data .payment_request_data .supported_networks, - merchant_identifier: applepay_metadata - .data - .session_token_data - .merchant_identifier, + merchant_identifier: Some( + applepay_metadata + .data + .session_token_data + .merchant_identifier, + ), }; - let response_router_data = types::PaymentsSessionRouterData { + let delayed_response = connectors_with_delayed_response.contains(&connector_name); + + if delayed_response { + let delayed_response_apple_pay_session = + payment_types::ApplePaySessionResponse::NoSessionResponse; + create_apple_pay_session_response( + router_data, + delayed_response_apple_pay_session, + None, // Apple pay payment request will be none for delayed session response + connector_name.to_string(), + delayed_response, + payment_types::NextActionCall::SessionToken, + None, //Response Id will be none for delayed session response + ) + } else { + let applepay_session_request = mk_applepay_session_request(state, router_data)?; + let response = services::call_connector_api(state, applepay_session_request).await; + log_session_response_if_error(&response); + + let session_response = response + .ok() + .and_then(|apple_pay_res| { + apple_pay_res + .map(|res| { + let response: Result< + payment_types::NoThirdPartySdkSessionResponse, + Report, + > = res.response.parse_struct("NoThirdPartySdkSessionResponse"); + response.ok() + }) + .ok() + }) + .flatten(); + + create_apple_pay_session_response( + router_data, + payment_types::ApplePaySessionResponse::NoThirdPartySdk(session_response), + Some(applepay_payment_request), + connector_name.to_string(), + delayed_response, + payment_types::NextActionCall::Confirm, + None, // Response Id will be none for No third party sdk response + ) + } +} + +fn create_apple_pay_session_response( + router_data: &types::PaymentsSessionRouterData, + session_response: payment_types::ApplePaySessionResponse, + apple_pay_payment_request: Option, + connector_name: String, + delayed_response: bool, + next_action: payment_types::NextActionCall, + response_id: Option, +) -> RouterResult { + Ok(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(), + payment_request_data: apple_pay_payment_request, + connector: connector_name, + delayed_session_token: delayed_response, + sdk_next_action: { payment_types::SdkNextAction { next_action } }, }, )), + response_id, }), ..router_data.clone() - }; - - Ok(response_router_data) + }) } fn create_gpay_session_token( @@ -271,6 +310,7 @@ fn create_gpay_session_token( connector: connector.connector_name.to_string(), }, )), + response_id: None, }), ..router_data.clone() }; @@ -278,6 +318,18 @@ fn create_gpay_session_token( Ok(response_router_data) } +fn log_session_response_if_error( + response: &Result, Report>, +) { + if let Err(error) = response.as_ref() { + logger::error!(?error); + }; + response + .as_ref() + .ok() + .map(|res| res.as_ref().map_err(|error| logger::error!(?error))); +} + impl types::PaymentsSessionRouterData { pub async fn decide_flow<'a, 'b>( &'b self, diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 90058f627d..36bb9b3812 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -153,6 +153,7 @@ impl GetTracker, api::PaymentsCancelRequest> connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 7aec05f375..f541db8fa8 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -159,6 +159,7 @@ impl GetTracker, api::PaymentsCaptu connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index cf7ad77a68..768b5e09d8 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -204,6 +204,7 @@ impl GetTracker, api::PaymentsRequest> for Co connector_customer_id: None, ephemeral_key: None, redirect_response, + delayed_session_token: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 7dc60813ea..7bd3d44be0 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -247,6 +247,7 @@ impl GetTracker, api::PaymentsRequest> for Pa connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 2ece098c0d..8becaae126 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -252,6 +252,7 @@ impl GetTracker, api::PaymentsRequest> for Pa connector_customer_id: None, ephemeral_key, redirect_response: None, + delayed_session_token: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs index 1a3406dc87..81fb8002cd 100644 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ b/crates/router/src/core/payments/operations/payment_method_validate.rs @@ -182,6 +182,7 @@ impl GetTracker, api::VerifyRequest> for Paym connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, Some(payments::CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 08ca7f678c..2d8aadafb3 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -172,6 +172,7 @@ impl GetTracker, api::PaymentsSessionRequest> connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: request.delayed_session_token, }, Some(customer_details), )) @@ -376,15 +377,15 @@ where for (connector, payment_method_type, business_sub_label) in connector_and_supporting_payment_method_type { - if let Ok(connector_data) = api::ConnectorData::get_connector_by_name( - connectors, - &connector, - api::GetToken::from(payment_method_type), - ) - .map_err(|err| { - logger::error!(session_token_error=?err); - err - }) { + let connector_type = + get_connector_type_for_session_token(payment_method_type, request, &connector); + if let Ok(connector_data) = + api::ConnectorData::get_connector_by_name(connectors, &connector, connector_type) + .map_err(|err| { + logger::error!(session_token_error=?err); + err + }) + { session_connector_data.push(api::SessionConnectorData { payment_method_type, connector: connector_data, @@ -411,11 +412,11 @@ impl From for api::GetToken { pub fn get_connector_type_for_session_token( payment_method_type: api_models::enums::PaymentMethodType, - _request: &api::PaymentsSessionRequest, - connector: String, + request: &api::PaymentsSessionRequest, + connector: &str, ) -> api::GetToken { if payment_method_type == api_models::enums::PaymentMethodType::ApplePay { - if connector == *"bluesnap" { + if is_apple_pay_get_token_connector(connector, request) { api::GetToken::Connector } else { api::GetToken::ApplePayMetadata @@ -424,3 +425,17 @@ pub fn get_connector_type_for_session_token( api::GetToken::from(payment_method_type) } } + +pub fn is_apple_pay_get_token_connector( + connector: &str, + request: &api::PaymentsSessionRequest, +) -> bool { + match connector { + "bluesnap" => true, + "trustpay" => request + .delayed_session_token + .and_then(|delay| delay.then_some(true)) + .is_some(), + _ => false, + } +} diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 2fbb1bd601..89b78525e9 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -144,6 +144,7 @@ impl GetTracker, api::PaymentsStartRequest> f connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, Some(customer_details), )) diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 731e799040..32aadcd91d 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -291,6 +291,7 @@ async fn get_tracker_for_sync< connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, None, )) diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index f8e206267d..472978c197 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -306,6 +306,7 @@ impl GetTracker, api::PaymentsRequest> for Pa connector_customer_id: None, ephemeral_key: None, redirect_response: None, + delayed_session_token: None, }, Some(CustomerDetails { customer_id: request.customer_id.clone(), diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 183ed60cf2..69ac4e2594 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -200,6 +200,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsSessionResponse, api_models::payments::SessionToken, api_models::payments::ApplePaySessionResponse, + api_models::payments::ThirdPartySdkSessionResponse, + api_models::payments::NoThirdPartySdkSessionResponse, + api_models::payments::SecretInfoToInitiateSdk, api_models::payments::ApplePayPaymentRequest, api_models::payments::AmountInfo, api_models::payments::GooglePayWalletData, @@ -215,6 +218,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::KlarnaSessionTokenResponse, api_models::payments::PaypalSessionTokenResponse, api_models::payments::ApplepaySessionTokenResponse, + api_models::payments::SdkNextAction, + api_models::payments::NextActionCall, api_models::payments::GpayTokenizationData, api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index cb9647a291..11b24a752d 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -375,6 +375,7 @@ pub enum PaymentsResponseData { }, SessionResponse { session_token: api::SessionToken, + response_id: Option, }, SessionTokenResponse { session_token: String, diff --git a/crates/storage_models/src/payment_attempt.rs b/crates/storage_models/src/payment_attempt.rs index f7f265336a..edfc67532c 100644 --- a/crates/storage_models/src/payment_attempt.rs +++ b/crates/storage_models/src/payment_attempt.rs @@ -159,6 +159,9 @@ pub enum PaymentAttemptUpdate { error_code: Option>, error_message: Option>, }, + SessionUpdate { + connector_transaction_id: Option, + }, StatusUpdate { status: storage_enums::AttemptStatus, }, @@ -390,6 +393,12 @@ impl From for PaymentAttemptUpdateInternal { preprocessing_step_id, ..Default::default() }, + PaymentAttemptUpdate::SessionUpdate { + connector_transaction_id, + } => Self { + connector_transaction_id, + ..Default::default() + }, } } }