feat: Session flow for Apple Pay trustpay (#1155)

This commit is contained in:
Sangamesh Kulkarni
2023-06-07 20:27:19 +05:30
committed by GitHub
parent d21fcc7bfc
commit a6e91a828b
23 changed files with 541 additions and 115 deletions

View File

@ -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"

View File

@ -1620,6 +1620,8 @@ pub struct PaymentsSessionRequest {
/// Merchant connector details used to make payments.
#[schema(value_type = Option<MerchantConnectorDetailsWrap>)]
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
/// Identifier for the delayed session response
pub delayed_session_token: Option<bool>,
}
#[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<ApplePayPaymentRequest>,
/// 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<NoThirdPartySdkSessionResponse>),
/// 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<String>,
// Authorization secrets used by client for payment
#[schema(value_type = String)]
pub payment: Secret<String>,
}
#[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<String>,
/// The list of supported networks
pub supported_networks: Vec<String>,
pub merchant_identifier: String,
pub merchant_identifier: Option<String>,
}
#[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<String>,
/// The total amount for the payment
pub amount: String,
}

View File

@ -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<api_models::enums::Connector>,
}
fn delayed_session_deser<'a, D>(
deserializer: D,
) -> Result<HashSet<api_models::enums::Connector>, D::Error>
where
D: Deserializer<'a>,
{
let value = <String>::deserialize(deserializer)?;
value
.trim()
.split(',')
.map(api_models::enums::Connector::from_str)
.collect::<Result<_, _>>()
.map_err(D::Error::custom)
}
impl Settings {
pub fn new() -> ApplicationResult<Self> {
Self::with_config_path(None)

View File

@ -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<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
let wallet_token = consts::BASE64_ENGINE
.decode(response.wallet_token.clone().expose())
.into_report()
.change_context(errors::ConnectorError::ParsingFailed)?;
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let session_response: api_models::payments::ApplePaySessionResponse = wallet_token[..]
.parse_struct("ApplePayResponse")
.change_context(errors::ConnectorError::ParsingFailed)?;
let session_response: api_models::payments::NoThirdPartySdkSessionResponse =
wallet_token[..]
.parse_struct("NoThirdPartySdkSessionResponse")
.change_context(errors::ConnectorError::ParsingFailed)?;
let metadata = item.data.get_connector_meta()?.expose();
let applepay_metadata = metadata
@ -303,13 +304,16 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: types::api::SessionToken::ApplePay(Box::new(
api_models::payments::ApplepaySessionTokenResponse {
session_token_data: session_response,
payment_request_data: api_models::payments::ApplePayPaymentRequest {
session_token_data:
api_models::payments::ApplePaySessionResponse::NoThirdPartySdk(Some(
session_response,
)),
payment_request_data: Some(api_models::payments::ApplePayPaymentRequest {
country_code: item.data.get_billing_country()?,
currency_code: item.data.request.currency.to_string(),
total: api_models::payments::AmountInfo {
label: applepay_metadata.data.payment_request_data.label,
total_type: "final".to_string(),
total_type: Some("final".to_string()),
amount: item.data.request.amount.to_string(),
},
merchant_capabilities: applepay_metadata
@ -320,14 +324,23 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
.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,
),
}),
connector: "bluesnap".to_string(),
delayed_session_token: false,
sdk_next_action: {
payments::SdkNextAction {
next_action: payments::NextActionCall::Confirm,
}
},
},
)),
response_id: None,
}),
..item.data
})

View File

@ -249,6 +249,7 @@ impl<F, T>
session_token: item.response.client_token.value,
},
)),
response_id: None,
}),
..item.data
})

View File

@ -78,6 +78,7 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>>
session_id: response.session_id.clone(),
},
)),
response_id: None,
}),
..item.data
})

View File

@ -339,6 +339,87 @@ impl api::PaymentSession for Trustpay {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsSessionRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, 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<String, errors::ConnectorError> {
Ok(format!("{}{}", self.base_url(connectors), "api/v1/intent"))
}
fn get_request_body(
&self,
req: &types::PaymentsSessionRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let create_intent_req = trustpay::TrustpayCreateIntentRequest::try_from(req)?;
let trustpay_req =
utils::Encode::<trustpay::TrustpayCreateIntentRequest>::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<Option<services::Request>, 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<types::PaymentsSessionRouterData, errors::ConnectorError> {
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<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::PaymentAuthorize for Trustpay {}

View File

@ -801,6 +801,128 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, TrustpayAuthUpdateResponse, T, t
}
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TrustpayCreateIntentRequest {
pub amount: String,
pub currency: String,
// If true, Apple Pay will be initialized
pub init_apple_pay: Option<bool>,
}
impl TryFrom<&types::PaymentsSessionRouterData> for TrustpayCreateIntentRequest {
type Error = Error;
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
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<String>,
pub payment: Secret<String>,
}
#[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<String>,
pub merchant_capabilities: Vec<String>,
pub total: ApplePayTotalInfo,
}
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTotalInfo {
pub label: String,
pub amount: String,
}
impl TryFrom<types::PaymentsSessionResponseRouterData<TrustpayCreateIntentResponse>>
for types::PaymentsSessionRouterData
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::PaymentsSessionResponseRouterData<TrustpayCreateIntentResponse>,
) -> Result<Self, Self::Error> {
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<SdkSecretInfo> for api_models::payments::SecretInfoToInitiateSdk {
fn from(value: SdkSecretInfo) -> Self {
Self {
display: value.display,
payment: value.payment,
}
}
}
impl From<ApplePayTotalInfo> 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 {

View File

@ -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<F, Op, Req>(
pub async fn get_session_tokens_and_persist_if_required<F, Op, Req>(
state: &AppState,
merchant_account: &domain::MerchantAccount,
connectors: Vec<api::SessionConnectorData>,
@ -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<F>(
state: &AppState,
merchant_account: &domain::MerchantAccount,
payment_data: &PaymentData<F>,
response_id: Option<String>,
) -> 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<F, Req>(
state: &AppState,
customer: &Option<domain::Customer>,
@ -975,6 +1015,7 @@ where
pub connector_customer_id: Option<String>,
pub ephemeral_key: Option<ephemeral_key::EphemeralKey>,
pub redirect_response: Option<api_models::payments::RedirectResponse>,
pub delayed_session_token: Option<bool>,
}
#[derive(Debug, Default)]
@ -1085,7 +1126,7 @@ pub async fn list_payments(
) -> RouterResponse<api::PaymentListResponse> {
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;

View File

@ -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<api::Session, types::PaymentsSessionData> 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<common_utils::pii::SecretSerdeValue>,
) -> RouterResult<payment_types::ApplepaySessionTokenData> {
connector_metadata
.parse_value::<payment_types::ApplepaySessionTokenData>("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<services::Request> {
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<types::PaymentsSessionRouterData> {
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<common_utils::errors::ParsingError>,
> = 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<payment_types::ApplePayPaymentRequest>,
connector_name: String,
delayed_response: bool,
next_action: payment_types::NextActionCall,
response_id: Option<String>,
) -> RouterResult<types::PaymentsSessionRouterData> {
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<Result<types::Response, types::Response>, Report<errors::ApiClientError>>,
) {
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,

View File

@ -153,6 +153,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
connector_customer_id: None,
ephemeral_key: None,
redirect_response: None,
delayed_session_token: None,
},
None,
))

View File

@ -159,6 +159,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
connector_customer_id: None,
ephemeral_key: None,
redirect_response: None,
delayed_session_token: None,
},
None,
))

View File

@ -204,6 +204,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),

View File

@ -247,6 +247,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),

View File

@ -252,6 +252,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),

View File

@ -182,6 +182,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),

View File

@ -172,6 +172,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<api_models::enums::PaymentMethodType> 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,
}
}

View File

@ -144,6 +144,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
connector_customer_id: None,
ephemeral_key: None,
redirect_response: None,
delayed_session_token: None,
},
Some(customer_details),
))

View File

@ -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,
))

View File

@ -306,6 +306,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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(),

View File

@ -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,

View File

@ -375,6 +375,7 @@ pub enum PaymentsResponseData {
},
SessionResponse {
session_token: api::SessionToken,
response_id: Option<String>,
},
SessionTokenResponse {
session_token: String,

View File

@ -159,6 +159,9 @@ pub enum PaymentAttemptUpdate {
error_code: Option<Option<String>>,
error_message: Option<Option<String>>,
},
SessionUpdate {
connector_transaction_id: Option<String>,
},
StatusUpdate {
status: storage_enums::AttemptStatus,
},
@ -390,6 +393,12 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
preprocessing_step_id,
..Default::default()
},
PaymentAttemptUpdate::SessionUpdate {
connector_transaction_id,
} => Self {
connector_transaction_id,
..Default::default()
},
}
}
}