mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 20:23:43 +08:00
feat: Session flow for Apple Pay trustpay (#1155)
This commit is contained in:
committed by
GitHub
parent
d21fcc7bfc
commit
a6e91a828b
@ -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"
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
})
|
||||
|
||||
@ -249,6 +249,7 @@ impl<F, T>
|
||||
session_token: item.response.client_token.value,
|
||||
},
|
||||
)),
|
||||
response_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
|
||||
@ -78,6 +78,7 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<KlarnaSessionResponse>>
|
||||
session_id: response.session_id.clone(),
|
||||
},
|
||||
)),
|
||||
response_id: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
))
|
||||
|
||||
@ -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,
|
||||
))
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
))
|
||||
|
||||
@ -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,
|
||||
))
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -375,6 +375,7 @@ pub enum PaymentsResponseData {
|
||||
},
|
||||
SessionResponse {
|
||||
session_token: api::SessionToken,
|
||||
response_id: Option<String>,
|
||||
},
|
||||
SessionTokenResponse {
|
||||
session_token: String,
|
||||
|
||||
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user