feat: applepay through trustpay (#1422)

Co-authored-by: dracarys18 <karthikey.hegde@juspay.in>
This commit is contained in:
Sangamesh Kulkarni
2023-06-18 20:36:57 +05:30
committed by GitHub
parent 641995371d
commit 8032e0290b
21 changed files with 694 additions and 147 deletions

View File

@ -112,6 +112,9 @@ basilisk_host = "" # Basilisk host
locker_setup = "legacy_locker" # With locker to use while in the deployed environment (eg. legacy_locker, basilisk_locker) locker_setup = "legacy_locker" # With locker to use while in the deployed environment (eg. legacy_locker, basilisk_locker)
locker_signing_key_id = "1" # Key_id to sign basilisk hs locker locker_signing_key_id = "1" # Key_id to sign basilisk hs locker
[delayed_session_response]
connectors_with_delayed_session_response = "trustpay" # List of connectors which has delayed session response
[jwekey] # 4 priv/pub key pair [jwekey] # 4 priv/pub key pair
locker_key_identifier1 = "" # key identifier for key rotation , should be same as basilisk locker_key_identifier1 = "" # key identifier for key rotation , should be same as basilisk
locker_key_identifier2 = "" # key identifier for key rotation , should be same as basilisk locker_key_identifier2 = "" # key identifier for key rotation , should be same as basilisk

View File

@ -262,3 +262,6 @@ refund_duration = 1000
refund_tolerance = 100 refund_tolerance = 100
refund_retrieve_duration = 500 refund_retrieve_duration = 500
refund_retrieve_tolerance = 100 refund_retrieve_tolerance = 100
[delayed_session_response]
connectors_with_delayed_session_response = "trustpay"

View File

@ -148,6 +148,9 @@ cards = [
"zen", "zen",
] ]
[delayed_session_response]
connectors_with_delayed_session_response = "trustpay"
[scheduler] [scheduler]
stream = "SCHEDULER_STREAM" stream = "SCHEDULER_STREAM"

View File

@ -829,6 +829,8 @@ pub enum WalletData {
ApplePay(ApplePayWalletData), ApplePay(ApplePayWalletData),
/// Wallet data for apple pay redirect flow /// Wallet data for apple pay redirect flow
ApplePayRedirect(Box<ApplePayRedirectData>), ApplePayRedirect(Box<ApplePayRedirectData>),
/// Wallet data for apple pay third party sdk flow
ApplePayThirdPartySdk(Box<ApplePayThirdPartySdkData>),
/// The wallet data for Google pay /// The wallet data for Google pay
GooglePay(GooglePayWalletData), GooglePay(GooglePayWalletData),
/// Wallet data for google pay redirect flow /// Wallet data for google pay redirect flow
@ -864,6 +866,9 @@ pub struct ApplePayRedirectData {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct GooglePayRedirectData {} pub struct GooglePayRedirectData {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct ApplePayThirdPartySdkData {}
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPayRedirection {} pub struct WeChatPayRedirection {}
@ -1125,6 +1130,8 @@ pub enum NextActionData {
DisplayBankTransferInformation { DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: BankTransferNextStepsData, bank_transfer_steps_and_charges_details: BankTransferNextStepsData,
}, },
/// contains third party sdk session token response
ThirdPartySdkSessionToken { session_token: Option<SessionToken> },
} }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1679,7 +1686,7 @@ pub struct PaymentsSessionRequest {
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>, pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayAllowedMethodsParameters { pub struct GpayAllowedMethodsParameters {
/// The list of allowed auth methods (ex: 3DS, No3DS, PAN_ONLY etc) /// The list of allowed auth methods (ex: 3DS, No3DS, PAN_ONLY etc)
pub allowed_auth_methods: Vec<String>, pub allowed_auth_methods: Vec<String>,
@ -1687,7 +1694,7 @@ pub struct GpayAllowedMethodsParameters {
pub allowed_card_networks: Vec<String>, pub allowed_card_networks: Vec<String>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayTokenParameters { pub struct GpayTokenParameters {
/// The name of the connector /// The name of the connector
pub gateway: String, pub gateway: String,
@ -1703,7 +1710,7 @@ pub struct GpayTokenParameters {
pub stripe_publishable_key: Option<String>, pub stripe_publishable_key: Option<String>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayTokenizationSpecification { pub struct GpayTokenizationSpecification {
/// The token specification type(ex: PAYMENT_GATEWAY) /// The token specification type(ex: PAYMENT_GATEWAY)
#[serde(rename = "type")] #[serde(rename = "type")]
@ -1712,7 +1719,7 @@ pub struct GpayTokenizationSpecification {
pub parameters: GpayTokenParameters, pub parameters: GpayTokenParameters,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayAllowedPaymentMethods { pub struct GpayAllowedPaymentMethods {
/// The type of payment method /// The type of payment method
#[serde(rename = "type")] #[serde(rename = "type")]
@ -1723,7 +1730,7 @@ pub struct GpayAllowedPaymentMethods {
pub tokenization_specification: GpayTokenizationSpecification, pub tokenization_specification: GpayTokenizationSpecification,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayTransactionInfo { pub struct GpayTransactionInfo {
/// The country code /// The country code
#[schema(value_type = CountryAlpha2, example = "US")] #[schema(value_type = CountryAlpha2, example = "US")]
@ -1736,7 +1743,7 @@ pub struct GpayTransactionInfo {
pub total_price: String, pub total_price: String,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GpayMerchantInfo { pub struct GpayMerchantInfo {
/// The name of the merchant /// The name of the merchant
pub merchant_name: String, pub merchant_name: String,
@ -1802,7 +1809,7 @@ pub struct SessionTokenInfo {
pub initiative_context: String, pub initiative_context: String,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(tag = "wallet_name")] #[serde(tag = "wallet_name")]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum SessionToken { pub enum SessionToken {
@ -1814,9 +1821,11 @@ pub enum SessionToken {
Paypal(Box<PaypalSessionTokenResponse>), Paypal(Box<PaypalSessionTokenResponse>),
/// The session response structure for Apple Pay /// The session response structure for Apple Pay
ApplePay(Box<ApplepaySessionTokenResponse>), ApplePay(Box<ApplepaySessionTokenResponse>),
/// Whenever there is no session token response or an error in session response
NoSessionTokenReceived,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct GpaySessionTokenResponse { pub struct GpaySessionTokenResponse {
/// The merchant info /// The merchant info
@ -1828,7 +1837,7 @@ pub struct GpaySessionTokenResponse {
pub connector: String, pub connector: String,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct KlarnaSessionTokenResponse { pub struct KlarnaSessionTokenResponse {
/// The session token for Klarna /// The session token for Klarna
@ -1837,26 +1846,58 @@ pub struct KlarnaSessionTokenResponse {
pub session_id: String, pub session_id: String,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct PaypalSessionTokenResponse { pub struct PaypalSessionTokenResponse {
/// The session token for PayPal /// The session token for PayPal
pub session_token: String, pub session_token: String,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct ApplepaySessionTokenResponse { pub struct ApplepaySessionTokenResponse {
/// Session object for Apple Pay /// Session object for Apple Pay
pub session_token_data: ApplePaySessionResponse, pub session_token_data: ApplePaySessionResponse,
/// Payment request object for Apple Pay /// 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, 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, Clone, serde::Serialize, ToSchema, serde::Deserialize)] #[derive(Debug, Eq, PartialEq, serde::Serialize, Clone, ToSchema)]
pub struct SdkNextAction {
/// The type of next action
pub next_action: NextActionCall,
}
#[derive(Debug, Eq, PartialEq, 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,
}
#[derive(Debug, Clone, Eq, PartialEq, 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(NoThirdPartySdkSessionResponse),
/// This is for the empty session response
NoSessionResponse,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)]
#[serde(rename_all(deserialize = "camelCase"))] #[serde(rename_all(deserialize = "camelCase"))]
pub struct ApplePaySessionResponse { pub struct NoThirdPartySdkSessionResponse {
/// Timestamp at which session is requested /// Timestamp at which session is requested
pub epoch_timestamp: u64, pub epoch_timestamp: u64,
/// Timestamp at which session expires /// Timestamp at which session expires
@ -1881,7 +1922,22 @@ pub struct ApplePaySessionResponse {
pub psp_id: String, pub psp_id: String,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)]
pub struct ThirdPartySdkSessionResponse {
pub secrets: SecretInfoToInitiateSdk,
}
#[derive(Debug, Clone, Eq, PartialEq, 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, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)]
pub struct ApplePayPaymentRequest { pub struct ApplePayPaymentRequest {
/// The code for country /// The code for country
#[schema(value_type = CountryAlpha2, example = "US")] #[schema(value_type = CountryAlpha2, example = "US")]
@ -1894,16 +1950,16 @@ pub struct ApplePayPaymentRequest {
pub merchant_capabilities: Vec<String>, pub merchant_capabilities: Vec<String>,
/// The list of supported networks /// The list of supported networks
pub supported_networks: Vec<String>, pub supported_networks: Vec<String>,
pub merchant_identifier: String, pub merchant_identifier: Option<String>,
} }
#[derive(Debug, Clone, serde::Serialize, ToSchema, serde::Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)]
pub struct AmountInfo { pub struct AmountInfo {
/// The label must be the name of the merchant. /// The label must be the name of the merchant.
pub label: String, pub label: String,
/// A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending. /// A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending.
#[serde(rename = "type")] #[serde(rename = "type")]
pub total_type: String, pub total_type: Option<String>,
/// The total amount for the payment /// The total amount for the payment
pub amount: String, pub amount: String,
} }

View File

@ -657,6 +657,9 @@ pub enum StripeNextAction {
DisplayBankTransferInformation { DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData, bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData,
}, },
ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -677,5 +680,8 @@ pub(crate) fn into_stripe_next_action(
} => StripeNextAction::DisplayBankTransferInformation { } => StripeNextAction::DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details, bank_transfer_steps_and_charges_details,
}, },
payments::NextActionData::ThirdPartySdkSessionToken { session_token } => {
StripeNextAction::ThirdPartySdkSessionToken { session_token }
}
}) })
} }

View File

@ -317,6 +317,9 @@ pub enum StripeNextAction {
DisplayBankTransferInformation { DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData, bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData,
}, },
ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -337,6 +340,9 @@ pub(crate) fn into_stripe_next_action(
} => StripeNextAction::DisplayBankTransferInformation { } => StripeNextAction::DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details, bank_transfer_steps_and_charges_details,
}, },
payments::NextActionData::ThirdPartySdkSessionToken { session_token } => {
StripeNextAction::ThirdPartySdkSessionToken { session_token }
}
}) })
} }

View File

@ -83,6 +83,7 @@ pub struct Settings {
pub dummy_connector: DummyConnector, pub dummy_connector: DummyConnector,
#[cfg(feature = "email")] #[cfg(feature = "email")]
pub email: EmailSettings, pub email: EmailSettings,
pub delayed_session_response: DelayedSessionConfig,
} }
#[derive(Debug, Deserialize, Clone, Default)] #[derive(Debug, Deserialize, Clone, Default)]
@ -507,6 +508,27 @@ pub struct FileUploadConfig {
pub bucket_name: String, 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 { impl Settings {
pub fn new() -> ApplicationResult<Self> { pub fn new() -> ApplicationResult<Self> {
Self::with_config_path(None) 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 base64::Engine;
use common_utils::{ use common_utils::{
errors::CustomResult, errors::CustomResult,
@ -321,11 +321,12 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
let wallet_token = consts::BASE64_ENGINE let wallet_token = consts::BASE64_ENGINE
.decode(response.wallet_token.clone().expose()) .decode(response.wallet_token.clone().expose())
.into_report() .into_report()
.change_context(errors::ConnectorError::ParsingFailed)?; .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let session_response: api_models::payments::ApplePaySessionResponse = wallet_token[..] let session_response: api_models::payments::NoThirdPartySdkSessionResponse =
.parse_struct("ApplePayResponse") wallet_token[..]
.change_context(errors::ConnectorError::ParsingFailed)?; .parse_struct("NoThirdPartySdkSessionResponse")
.change_context(errors::ConnectorError::ParsingFailed)?;
let metadata = item.data.get_connector_meta()?.expose(); let metadata = item.data.get_connector_meta()?.expose();
let applepay_metadata = metadata let applepay_metadata = metadata
@ -338,13 +339,16 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
response: Ok(types::PaymentsResponseData::SessionResponse { response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: types::api::SessionToken::ApplePay(Box::new( session_token: types::api::SessionToken::ApplePay(Box::new(
api_models::payments::ApplepaySessionTokenResponse { api_models::payments::ApplepaySessionTokenResponse {
session_token_data: session_response, session_token_data:
payment_request_data: api_models::payments::ApplePayPaymentRequest { api_models::payments::ApplePaySessionResponse::NoThirdPartySdk(
session_response,
),
payment_request_data: Some(api_models::payments::ApplePayPaymentRequest {
country_code: item.data.get_billing_country()?, country_code: item.data.get_billing_country()?,
currency_code: item.data.request.currency.to_string(), currency_code: item.data.request.currency.to_string(),
total: api_models::payments::AmountInfo { total: api_models::payments::AmountInfo {
label: applepay_metadata.data.payment_request_data.label, 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(), amount: item.data.request.amount.to_string(),
}, },
merchant_capabilities: applepay_metadata merchant_capabilities: applepay_metadata
@ -355,12 +359,20 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
.data .data
.payment_request_data .payment_request_data
.supported_networks, .supported_networks,
merchant_identifier: applepay_metadata merchant_identifier: Some(
.data applepay_metadata
.session_token_data .data
.merchant_identifier, .session_token_data
}, .merchant_identifier,
),
}),
connector: "bluesnap".to_string(), connector: "bluesnap".to_string(),
delayed_session_token: false,
sdk_next_action: {
payments::SdkNextAction {
next_action: payments::NextActionCall::Confirm,
}
},
}, },
)), )),
}), }),

View File

@ -2167,8 +2167,11 @@ impl<F, T>
}; };
Ok(Self { Ok(Self {
response: Ok(types::PaymentsResponseData::PreProcessingResponse { response: Ok(types::PaymentsResponseData::PreProcessingResponse {
pre_processing_id: item.response.id, pre_processing_id: types::PreprocessingResponseId::PreProcessingId(
item.response.id,
),
connector_metadata: Some(connector_metadata), connector_metadata: Some(connector_metadata),
session_token: None,
}), }),
status, status,
..item.data ..item.data

View File

@ -343,6 +343,102 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
{ {
} }
impl api::PaymentsPreProcessing for Trustpay {}
impl
ConnectorIntegration<
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsPreProcessingType::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::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}{}", self.base_url(connectors), "api/v1/intent"))
}
fn get_request_body(
&self,
req: &types::PaymentsPreProcessingRouterData,
) -> 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::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let req = Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.attach_default_headers()
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.body(types::PaymentsPreProcessingType::get_request_body(
self, req,
)?)
.build(),
);
Ok(req)
}
fn handle_response(
&self,
data: &types::PaymentsPreProcessingRouterData,
res: Response,
) -> CustomResult<types::PaymentsPreProcessingRouterData, 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::PaymentSession for Trustpay {} impl api::PaymentSession for Trustpay {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData> impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>

View File

@ -16,6 +16,7 @@ use crate::{
core::errors, core::errors,
services, services,
types::{self, api, storage::enums, BrowserInformation}, types::{self, api, storage::enums, BrowserInformation},
utils::OptionExt,
}; };
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
@ -474,7 +475,7 @@ pub struct PaymentsResponseCards {
pub status: i64, pub status: i64,
pub description: Option<String>, pub description: Option<String>,
pub instance_id: String, pub instance_id: String,
pub payment_status: String, pub payment_status: Option<String>,
pub payment_description: Option<String>, pub payment_description: Option<String>,
pub redirect_url: Option<Url>, pub redirect_url: Option<Url>,
pub redirect_params: Option<HashMap<String, String>>, pub redirect_params: Option<HashMap<String, String>>,
@ -553,8 +554,13 @@ fn handle_cards_response(
), ),
errors::ConnectorError, errors::ConnectorError,
> { > {
// By default, payment status is pending(000.200.000 status code)
let (status, msg) = get_transaction_status( let (status, msg) = get_transaction_status(
response.payment_status.as_str(), response
.payment_status
.to_owned()
.unwrap_or("000.200.000".to_string())
.as_str(),
response.redirect_url.clone(), response.redirect_url.clone(),
)?; )?;
let form_fields = response.redirect_params.unwrap_or_default(); let form_fields = response.redirect_params.unwrap_or_default();
@ -567,7 +573,9 @@ fn handle_cards_response(
}); });
let error = if msg.is_some() { let error = if msg.is_some() {
Some(types::ErrorResponse { Some(types::ErrorResponse {
code: response.payment_status, code: response
.payment_status
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
message: msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), message: msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None, reason: None,
status_code, status_code,
@ -802,6 +810,164 @@ 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),
})
}
}
impl TryFrom<&types::PaymentsPreProcessingRouterData> for TrustpayCreateIntentRequest {
type Error = Error;
fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
Ok(Self {
amount: item
.request
.amount
.get_required_value("amount")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?
.to_string(),
currency: item
.request
.currency
.get_required_value("currency")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "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<F, T>
TryFrom<
types::ResponseRouterData<F, TrustpayCreateIntentResponse, T, types::PaymentsResponseData>,
> for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = Error;
fn try_from(
item: types::ResponseRouterData<
F,
TrustpayCreateIntentResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let response = item.response;
Ok(Self {
response: Ok(types::PaymentsResponseData::PreProcessingResponse {
connector_metadata: None,
pre_processing_id: types::PreprocessingResponseId::ConnectorTransactionId(
response.instance_id,
),
session_token: Some(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,
}
},
},
))),
}),
..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)] #[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TrustpayRefundRequestCards { pub struct TrustpayRefundRequestCards {

View File

@ -156,7 +156,7 @@ where
&merchant_account, &merchant_account,
connector, connector,
&operation, &operation,
&payment_data, &mut payment_data,
&customer, &customer,
call_connector_action, call_connector_action,
tokenization_action, tokenization_action,
@ -399,6 +399,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
.and_then(|next_action_data| match next_action_data { .and_then(|next_action_data| match next_action_data {
api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url), api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url),
api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None, api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None,
api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None
}) })
.ok_or(errors::ApiErrorResponse::InternalServerError) .ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report() .into_report()
@ -489,7 +490,7 @@ pub async fn call_connector_service<F, Op, Req>(
merchant_account: &domain::MerchantAccount, merchant_account: &domain::MerchantAccount,
connector: api::ConnectorData, connector: api::ConnectorData,
_operation: &Op, _operation: &Op,
payment_data: &PaymentData<F>, payment_data: &mut PaymentData<F>,
customer: &Option<domain::Customer>, customer: &Option<domain::Customer>,
call_connector_action: CallConnectorAction, call_connector_action: CallConnectorAction,
tokenization_action: TokenizationAction, tokenization_action: TokenizationAction,
@ -542,6 +543,14 @@ where
) )
.await?; .await?;
if let Ok(types::PaymentsResponseData::PreProcessingResponse {
session_token: Some(session_token),
..
}) = router_data.response.to_owned()
{
payment_data.sessions_token.push(session_token);
};
let router_data_res = if should_continue_payment { let router_data_res = if should_continue_payment {
router_data router_data
.decide_flows( .decide_flows(
@ -590,7 +599,6 @@ where
for session_connector_data in connectors.iter() { for session_connector_data in connectors.iter() {
let connector_id = session_connector_data.connector.connector.id(); let connector_id = session_connector_data.connector.connector.id();
let router_data = payment_data let router_data = payment_data
.construct_router_data(state, connector_id, merchant_account, customer) .construct_router_data(state, connector_id, merchant_account, customer)
.await?; .await?;
@ -612,10 +620,17 @@ where
let connector_name = session_connector.connector.connector_name.to_string(); let connector_name = session_connector.connector.connector_name.to_string();
match connector_res { match connector_res {
Ok(connector_response) => { Ok(connector_response) => {
if let Ok(types::PaymentsResponseData::SessionResponse { session_token }) = if let Ok(types::PaymentsResponseData::SessionResponse { session_token, .. }) =
connector_response.response connector_response.response
{ {
payment_data.sessions_token.push(session_token); // If session token is NoSessionTokenReceived, it is not pushed into the sessions_token as there is no response or there can be some error
// In case of error, that error is already logged
if !matches!(
session_token,
api_models::payments::SessionToken::NoSessionTokenReceived,
) {
payment_data.sessions_token.push(session_token);
}
} }
} }
Err(connector_error) => { Err(connector_error) => {
@ -716,17 +731,17 @@ where
} }
} }
async fn complete_preprocessing_steps_if_required<F, Req, Res>( async fn complete_preprocessing_steps_if_required<F, Req>(
state: &AppState, state: &AppState,
connector: &api::ConnectorData, connector: &api::ConnectorData,
payment_data: &PaymentData<F>, payment_data: &PaymentData<F>,
router_data: types::RouterData<F, Req, Res>, router_data: types::RouterData<F, Req, types::PaymentsResponseData>,
should_continue_payment: bool, should_continue_payment: bool,
) -> RouterResult<(types::RouterData<F, Req, Res>, bool)> ) -> RouterResult<(types::RouterData<F, Req, types::PaymentsResponseData>, bool)>
where where
F: Send + Clone + Sync, F: Send + Clone + Sync,
Req: Send + Sync, Req: Send + Sync,
types::RouterData<F, Req, Res>: Feature<F, Req> + Send, types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req> + Send,
dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>, dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>,
{ {
//TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check //TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check
@ -744,6 +759,16 @@ where
} }
_ => (router_data, should_continue_payment), _ => (router_data, should_continue_payment),
}, },
Some(api_models::payments::PaymentMethodData::Wallet(_)) => {
if connector.connector_name.to_string() == *"trustpay" {
(
router_data.preprocessing_steps(state, connector).await?,
false,
)
} else {
(router_data, should_continue_payment)
}
}
_ => (router_data, should_continue_payment), _ => (router_data, should_continue_payment),
}; };
@ -869,7 +894,6 @@ where
.payment_method .payment_method
.get_required_value("payment_method")?; .get_required_value("payment_method")?;
let payment_method_type = &payment_data.payment_attempt.payment_method_type; let payment_method_type = &payment_data.payment_attempt.payment_method_type;
let is_connector_tokenization_enabled = let is_connector_tokenization_enabled =
is_payment_method_tokenization_enabled_for_connector( is_payment_method_tokenization_enabled_for_connector(
state, state,

View File

@ -647,7 +647,6 @@ default_imp_for_pre_processing_steps!(
connector::Payu, connector::Payu,
connector::Rapyd, connector::Rapyd,
connector::Shift4, connector::Shift4,
connector::Trustpay,
connector::Worldline, connector::Worldline,
connector::Worldpay, connector::Worldpay,
connector::Zen connector::Zen

View File

@ -337,6 +337,7 @@ impl TryFrom<types::PaymentsAuthorizeData> for types::PaymentsPreProcessingData
Ok(Self { Ok(Self {
email: data.email, email: data.email,
currency: Some(data.currency), currency: Some(data.currency),
amount: Some(data.amount),
}) })
} }
} }

View File

@ -1,7 +1,7 @@
use api_models::payments as payment_types; use api_models::payments as payment_types;
use async_trait::async_trait; use async_trait::async_trait;
use common_utils::ext_traits::ByteSliceExt; use common_utils::ext_traits::ByteSliceExt;
use error_stack::{report, ResultExt}; use error_stack::{Report, ResultExt};
use super::{ConstructFlowSpecificData, Feature}; use super::{ConstructFlowSpecificData, Feature};
use crate::{ use crate::{
@ -10,7 +10,7 @@ use crate::{
errors::{self, ConnectorErrorExt, RouterResult}, errors::{self, ConnectorErrorExt, RouterResult},
payments::{self, access_token, transformers, PaymentData}, payments::{self, access_token, transformers, PaymentData},
}, },
headers, headers, logger,
routes::{self, metrics}, routes::{self, metrics},
services, services,
types::{self, api, domain}, types::{self, api, domain},
@ -78,18 +78,22 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio
} }
} }
fn mk_applepay_session_request( fn get_applepay_metadata(
state: &routes::AppState, connector_metadata: Option<common_utils::pii::SecretSerdeValue>,
router_data: &types::PaymentsSessionRouterData, ) -> RouterResult<payment_types::ApplepaySessionTokenData> {
) -> RouterResult<(services::Request, payment_types::ApplepaySessionTokenData)> { connector_metadata
let connector_metadata = router_data.connector_meta_data.clone();
let applepay_metadata = connector_metadata
.parse_value::<payment_types::ApplepaySessionTokenData>("ApplepaySessionTokenData") .parse_value::<payment_types::ApplepaySessionTokenData>("ApplepaySessionTokenData")
.change_context(errors::ApiErrorResponse::InvalidDataFormat { .change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_metadata".to_string(), field_name: "connector_metadata".to_string(),
expected_format: "applepay_metadata_format".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 { let request = payment_types::ApplepaySessionRequest {
merchant_identifier: applepay_metadata merchant_identifier: applepay_metadata
.data .data
@ -134,14 +138,10 @@ fn mk_applepay_session_request(
.clone(), .clone(),
)) ))
.add_certificate_key(Some( .add_certificate_key(Some(
applepay_metadata applepay_metadata.data.session_token_data.certificate_keys,
.data
.session_token_data
.certificate_keys
.clone(),
)) ))
.build(); .build();
Ok((session_request, applepay_metadata)) Ok(session_request)
} }
async fn create_applepay_session_token( async fn create_applepay_session_token(
@ -149,83 +149,138 @@ async fn create_applepay_session_token(
router_data: &types::PaymentsSessionRouterData, router_data: &types::PaymentsSessionRouterData,
connector: &api::ConnectorData, connector: &api::ConnectorData,
) -> RouterResult<types::PaymentsSessionRouterData> { ) -> RouterResult<types::PaymentsSessionRouterData> {
let (applepay_session_request, applepay_metadata) = let connectors_with_delayed_response = &state
mk_applepay_session_request(state, router_data)?; .conf
let response = services::call_connector_api(state, applepay_session_request) .delayed_session_response
.await .connectors_with_delayed_session_response;
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failure in calling connector api")?;
let session_response: payment_types::ApplePaySessionResponse = match response {
Ok(resp) => resp
.response
.parse_struct("ApplePaySessionResponse")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to parse ApplePaySessionResponse struct"),
Err(err) => {
let error_response: payment_types::ApplepayErrorResponse = err
.response
.parse_struct("ApplepayErrorResponse")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to parse ApplepayErrorResponse struct")?;
Err(
report!(errors::ApiErrorResponse::InternalServerError).attach_printable(format!(
"Failed with {} status code and the error response is {:?}",
err.status_code, error_response
)),
)
}
}?;
let amount_info = payment_types::AmountInfo { let connector_name = connector.connector_name;
label: applepay_metadata.data.payment_request_data.label, let delayed_response = connectors_with_delayed_response.contains(&connector_name);
total_type: "final".to_string(),
amount: connector::utils::to_currency_base_unit( if delayed_response {
router_data.request.amount, let delayed_response_apple_pay_session =
router_data.request.currency, Some(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::Confirm,
) )
.change_context(errors::ApiErrorResponse::InternalServerError) } else {
.attach_printable("Failed to convert currency to base unit")?, let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?;
};
let applepay_payment_request = payment_types::ApplePayPaymentRequest { let amount_info = payment_types::AmountInfo {
country_code: router_data label: applepay_metadata.data.payment_request_data.label,
.request total_type: Some("final".to_string()),
.country amount: connector::utils::to_currency_base_unit(
.to_owned() router_data.request.amount,
.get_required_value("country_code") router_data.request.currency,
.change_context(errors::ApiErrorResponse::MissingRequiredField { )
field_name: "country_code", .change_context(errors::ApiErrorResponse::PreconditionFailed {
message: "Failed to convert currency to base unit".to_string(),
})?, })?,
currency_code: router_data.request.currency.to_string(), };
total: amount_info,
merchant_capabilities: applepay_metadata
.data
.payment_request_data
.merchant_capabilities,
supported_networks: applepay_metadata
.data
.payment_request_data
.supported_networks,
merchant_identifier: applepay_metadata
.data
.session_token_data
.merchant_identifier,
};
let response_router_data = types::PaymentsSessionRouterData { let applepay_payment_request = payment_types::ApplePayPaymentRequest {
response: Ok(types::PaymentsResponseData::SessionResponse { country_code: router_data
session_token: payment_types::SessionToken::ApplePay(Box::new( .request
payment_types::ApplepaySessionTokenResponse { .country
session_token_data: session_response, .to_owned()
payment_request_data: applepay_payment_request, .get_required_value("country_code")
connector: connector.connector_name.to_string(), .change_context(errors::ApiErrorResponse::MissingRequiredField {
}, field_name: "country_code",
)), })?,
currency_code: router_data.request.currency.to_string(),
total: amount_info,
merchant_capabilities: applepay_metadata
.data
.payment_request_data
.merchant_capabilities,
supported_networks: applepay_metadata
.data
.payment_request_data
.supported_networks,
merchant_identifier: Some(
applepay_metadata
.data
.session_token_data
.merchant_identifier,
),
};
let applepay_session_request = mk_applepay_session_request(state, router_data)?;
let response = services::call_connector_api(state, applepay_session_request).await;
// logging the error if present in session call response
log_session_response_if_error(&response);
let apple_pay_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");
// logging the parsing failed error
if let Err(error) = response.as_ref() {
logger::error!(?error);
};
response.ok()
})
.ok()
})
.flatten();
let session_response =
apple_pay_session_response.map(payment_types::ApplePaySessionResponse::NoThirdPartySdk);
create_apple_pay_session_response(
router_data,
session_response,
Some(applepay_payment_request),
connector_name.to_string(),
delayed_response,
payment_types::NextActionCall::Confirm,
)
}
}
fn create_apple_pay_session_response(
router_data: &types::PaymentsSessionRouterData,
session_response: Option<payment_types::ApplePaySessionResponse>,
apple_pay_payment_request: Option<payment_types::ApplePayPaymentRequest>,
connector_name: String,
delayed_response: bool,
next_action: payment_types::NextActionCall,
) -> RouterResult<types::PaymentsSessionRouterData> {
match session_response {
Some(response) => Ok(types::PaymentsSessionRouterData {
response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: payment_types::SessionToken::ApplePay(Box::new(
payment_types::ApplepaySessionTokenResponse {
session_token_data: response,
payment_request_data: apple_pay_payment_request,
connector: connector_name,
delayed_session_token: delayed_response,
sdk_next_action: { payment_types::SdkNextAction { next_action } },
},
)),
}),
..router_data.clone()
}), }),
..router_data.clone() None => Ok(types::PaymentsSessionRouterData {
}; response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: payment_types::SessionToken::NoSessionTokenReceived,
Ok(response_router_data) }),
..router_data.clone()
}),
}
} }
fn create_gpay_session_token( fn create_gpay_session_token(
@ -278,6 +333,18 @@ fn create_gpay_session_token(
Ok(response_router_data) 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 { impl types::PaymentsSessionRouterData {
pub async fn decide_flow<'a, 'b>( pub async fn decide_flow<'a, 'b>(
&'b self, &'b self,

View File

@ -315,13 +315,28 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
types::PaymentsResponseData::PreProcessingResponse { types::PaymentsResponseData::PreProcessingResponse {
pre_processing_id, pre_processing_id,
connector_metadata, connector_metadata,
..
} => { } => {
let connector_transaction_id = match pre_processing_id.to_owned() {
types::PreprocessingResponseId::PreProcessingId(_) => None,
types::PreprocessingResponseId::ConnectorTransactionId(connector_txn_id) => {
Some(connector_txn_id)
}
};
let preprocessing_step_id = match pre_processing_id {
types::PreprocessingResponseId::PreProcessingId(pre_processing_id) => {
Some(pre_processing_id)
}
types::PreprocessingResponseId::ConnectorTransactionId(_) => None,
};
let payment_attempt_update = storage::PaymentAttemptUpdate::PreprocessingUpdate { let payment_attempt_update = storage::PaymentAttemptUpdate::PreprocessingUpdate {
status: router_data.status, status: router_data.status,
payment_method_id: Some(router_data.payment_method_id), payment_method_id: Some(router_data.payment_method_id),
connector_metadata, connector_metadata,
preprocessing_step_id: Some(pre_processing_id), preprocessing_step_id,
connector_transaction_id,
}; };
(Some(payment_attempt_update), None) (Some(payment_attempt_update), None)
} }
types::PaymentsResponseData::TransactionResponse { types::PaymentsResponseData::TransactionResponse {

View File

@ -377,15 +377,15 @@ where
for (connector, payment_method_type, business_sub_label) in for (connector, payment_method_type, business_sub_label) in
connector_and_supporting_payment_method_type connector_and_supporting_payment_method_type
{ {
if let Ok(connector_data) = api::ConnectorData::get_connector_by_name( let connector_type =
connectors, get_connector_type_for_session_token(payment_method_type, request, &connector);
&connector, if let Ok(connector_data) =
api::GetToken::from(payment_method_type), api::ConnectorData::get_connector_by_name(connectors, &connector, connector_type)
) .map_err(|err| {
.map_err(|err| { logger::error!(session_token_error=?err);
logger::error!(session_token_error=?err); err
err })
}) { {
session_connector_data.push(api::SessionConnectorData { session_connector_data.push(api::SessionConnectorData {
payment_method_type, payment_method_type,
connector: connector_data, connector: connector_data,
@ -412,11 +412,11 @@ impl From<api_models::enums::PaymentMethodType> for api::GetToken {
pub fn get_connector_type_for_session_token( pub fn get_connector_type_for_session_token(
payment_method_type: api_models::enums::PaymentMethodType, payment_method_type: api_models::enums::PaymentMethodType,
_request: &api::PaymentsSessionRequest, request: &api::PaymentsSessionRequest,
connector: String, connector: &str,
) -> api::GetToken { ) -> api::GetToken {
if payment_method_type == api_models::enums::PaymentMethodType::ApplePay { if payment_method_type == api_models::enums::PaymentMethodType::ApplePay {
if connector == *"bluesnap" { if is_apple_pay_get_token_connector(connector, request) {
api::GetToken::Connector api::GetToken::Connector
} else { } else {
api::GetToken::ApplePayMetadata api::GetToken::ApplePayMetadata
@ -425,3 +425,11 @@ pub fn get_connector_type_for_session_token(
api::GetToken::from(payment_method_type) api::GetToken::from(payment_method_type)
} }
} }
pub fn is_apple_pay_get_token_connector(
connector: &str,
_request: &api::PaymentsSessionRequest,
) -> bool {
// Add connectors here, which all are required to hit connector for session call
matches!(connector, "bluesnap")
}

View File

@ -169,6 +169,7 @@ where
payment_data.connector_response.authentication_data, payment_data.connector_response.authentication_data,
&operation, &operation,
payment_data.ephemeral_key, payment_data.ephemeral_key,
payment_data.sessions_token,
) )
} }
} }
@ -260,6 +261,7 @@ pub fn payments_to_payments_response<R, Op>(
redirection_data: Option<serde_json::Value>, redirection_data: Option<serde_json::Value>,
operation: &Op, operation: &Op,
ephemeral_key_option: Option<ephemeral_key::EphemeralKey>, ephemeral_key_option: Option<ephemeral_key::EphemeralKey>,
session_tokens: Vec<api::SessionToken>,
) -> RouterResponse<api::PaymentsResponse> ) -> RouterResponse<api::PaymentsResponse>
where where
Op: Debug, Op: Debug,
@ -337,6 +339,15 @@ where
})); }));
}; };
// next action check for third party sdk session (for ex: Apple pay through trustpay has third party sdk session response)
if third_party_sdk_session_next_action(&payment_attempt, operation) {
next_action_response = Some(
api_models::payments::NextActionData::ThirdPartySdkSessionToken {
session_token: session_tokens.get(0).cloned(),
},
)
}
let mut response: api::PaymentsResponse = Default::default(); let mut response: api::PaymentsResponse = Default::default();
let routed_through = payment_attempt.connector.clone(); let routed_through = payment_attempt.connector.clone();
@ -514,6 +525,34 @@ where
output output
} }
pub fn third_party_sdk_session_next_action<Op>(
payment_attempt: &storage::PaymentAttempt,
operation: &Op,
) -> bool
where
Op: Debug,
{
// If the operation is confirm, we will send session token response in next action
if format!("{operation:?}").eq("PaymentConfirm") {
payment_attempt
.connector
.as_ref()
.map(|connector| matches!(connector.as_str(), "trustpay"))
.and_then(|is_connector_supports_third_party_sdk| {
if is_connector_supports_third_party_sdk {
payment_attempt
.payment_method
.map(|pm| matches!(pm, storage_models::enums::PaymentMethod::Wallet))
} else {
Some(false)
}
})
.unwrap_or(false)
} else {
false
}
}
impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse {
fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self {
let pi = item.0; let pi = item.0;
@ -900,6 +939,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
Ok(Self { Ok(Self {
email: payment_data.email, email: payment_data.email,
currency: Some(payment_data.currency), currency: Some(payment_data.currency),
amount: Some(payment_data.amount.into()),
}) })
} }
} }

View File

@ -201,6 +201,9 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::PaymentsSessionResponse, api_models::payments::PaymentsSessionResponse,
api_models::payments::SessionToken, api_models::payments::SessionToken,
api_models::payments::ApplePaySessionResponse, api_models::payments::ApplePaySessionResponse,
api_models::payments::ThirdPartySdkSessionResponse,
api_models::payments::NoThirdPartySdkSessionResponse,
api_models::payments::SecretInfoToInitiateSdk,
api_models::payments::ApplePayPaymentRequest, api_models::payments::ApplePayPaymentRequest,
api_models::payments::AmountInfo, api_models::payments::AmountInfo,
api_models::payments::GooglePayWalletData, api_models::payments::GooglePayWalletData,
@ -216,6 +219,8 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::KlarnaSessionTokenResponse, api_models::payments::KlarnaSessionTokenResponse,
api_models::payments::PaypalSessionTokenResponse, api_models::payments::PaypalSessionTokenResponse,
api_models::payments::ApplepaySessionTokenResponse, api_models::payments::ApplepaySessionTokenResponse,
api_models::payments::SdkNextAction,
api_models::payments::NextActionCall,
api_models::payments::GpayTokenizationData, api_models::payments::GpayTokenizationData,
api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::GooglePayPaymentMethodInfo,
api_models::payments::ApplePayWalletData, api_models::payments::ApplePayWalletData,
@ -231,6 +236,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::ReceiverDetails, api_models::payments::ReceiverDetails,
api_models::payments::AchTransfer, api_models::payments::AchTransfer,
api_models::payments::ApplePayRedirectData, api_models::payments::ApplePayRedirectData,
api_models::payments::ApplePayThirdPartySdkData,
api_models::payments::GooglePayRedirectData, api_models::payments::GooglePayRedirectData,
api_models::payments::SepaBankTransferInstructions, api_models::payments::SepaBankTransferInstructions,
api_models::payments::BacsBankTransferInstructions, api_models::payments::BacsBankTransferInstructions,

View File

@ -270,6 +270,7 @@ pub struct PaymentMethodTokenizationData {
pub struct PaymentsPreProcessingData { pub struct PaymentsPreProcessingData {
pub email: Option<Email>, pub email: Option<Email>,
pub currency: Option<storage_enums::Currency>, pub currency: Option<storage_enums::Currency>,
pub amount: Option<i64>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -425,11 +426,18 @@ pub enum PaymentsResponseData {
related_transaction_id: Option<String>, related_transaction_id: Option<String>,
}, },
PreProcessingResponse { PreProcessingResponse {
pre_processing_id: String, pre_processing_id: PreprocessingResponseId,
connector_metadata: Option<serde_json::Value>, connector_metadata: Option<serde_json::Value>,
session_token: Option<api::SessionToken>,
}, },
} }
#[derive(Debug, Clone)]
pub enum PreprocessingResponseId {
PreProcessingId(String),
ConnectorTransactionId(String),
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub enum ResponseId { pub enum ResponseId {
ConnectorTransactionId(String), ConnectorTransactionId(String),

View File

@ -178,6 +178,7 @@ pub enum PaymentAttemptUpdate {
payment_method_id: Option<Option<String>>, payment_method_id: Option<Option<String>>,
connector_metadata: Option<serde_json::Value>, connector_metadata: Option<serde_json::Value>,
preprocessing_step_id: Option<String>, preprocessing_step_id: Option<String>,
connector_transaction_id: Option<String>,
}, },
} }
@ -394,12 +395,14 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
payment_method_id, payment_method_id,
connector_metadata, connector_metadata,
preprocessing_step_id, preprocessing_step_id,
connector_transaction_id,
} => Self { } => Self {
status: Some(status), status: Some(status),
payment_method_id, payment_method_id,
modified_at: Some(common_utils::date_time::now()), modified_at: Some(common_utils::date_time::now()),
connector_metadata, connector_metadata,
preprocessing_step_id, preprocessing_step_id,
connector_transaction_id,
..Default::default() ..Default::default()
}, },
} }