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_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
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

View File

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

View File

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

View File

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

View File

@ -657,6 +657,9 @@ pub enum StripeNextAction {
DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData,
},
ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>,
},
}
pub(crate) fn into_stripe_next_action(
@ -677,5 +680,8 @@ pub(crate) fn into_stripe_next_action(
} => StripeNextAction::DisplayBankTransferInformation {
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 {
bank_transfer_steps_and_charges_details: payments::BankTransferNextStepsData,
},
ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>,
},
}
pub(crate) fn into_stripe_next_action(
@ -337,6 +340,9 @@ pub(crate) fn into_stripe_next_action(
} => StripeNextAction::DisplayBankTransferInformation {
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,
#[cfg(feature = "email")]
pub email: EmailSettings,
pub delayed_session_response: DelayedSessionConfig,
}
#[derive(Debug, Deserialize, Clone, Default)]
@ -507,6 +508,27 @@ pub struct FileUploadConfig {
pub bucket_name: String,
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct DelayedSessionConfig {
#[serde(deserialize_with = "delayed_session_deser")]
pub connectors_with_delayed_session_response: HashSet<api_models::enums::Connector>,
}
fn delayed_session_deser<'a, D>(
deserializer: D,
) -> Result<HashSet<api_models::enums::Connector>, D::Error>
where
D: Deserializer<'a>,
{
let value = <String>::deserialize(deserializer)?;
value
.trim()
.split(',')
.map(api_models::enums::Connector::from_str)
.collect::<Result<_, _>>()
.map_err(D::Error::custom)
}
impl Settings {
pub fn new() -> ApplicationResult<Self> {
Self::with_config_path(None)

View File

@ -1,4 +1,4 @@
use api_models::enums as api_enums;
use api_models::{enums as api_enums, payments};
use base64::Engine;
use common_utils::{
errors::CustomResult,
@ -321,10 +321,11 @@ 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")
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();
@ -338,13 +339,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(
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
@ -355,12 +359,20 @@ impl TryFrom<types::PaymentsSessionResponseRouterData<BluesnapWalletTokenRespons
.data
.payment_request_data
.supported_networks,
merchant_identifier: applepay_metadata
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,
}
},
},
)),
}),

View File

@ -2167,8 +2167,11 @@ impl<F, T>
};
Ok(Self {
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),
session_token: None,
}),
status,
..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 ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>

View File

@ -16,6 +16,7 @@ use crate::{
core::errors,
services,
types::{self, api, storage::enums, BrowserInformation},
utils::OptionExt,
};
type Error = error_stack::Report<errors::ConnectorError>;
@ -474,7 +475,7 @@ pub struct PaymentsResponseCards {
pub status: i64,
pub description: Option<String>,
pub instance_id: String,
pub payment_status: String,
pub payment_status: Option<String>,
pub payment_description: Option<String>,
pub redirect_url: Option<Url>,
pub redirect_params: Option<HashMap<String, String>>,
@ -553,8 +554,13 @@ fn handle_cards_response(
),
errors::ConnectorError,
> {
// By default, payment status is pending(000.200.000 status code)
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(),
)?;
let form_fields = response.redirect_params.unwrap_or_default();
@ -567,7 +573,9 @@ fn handle_cards_response(
});
let error = if msg.is_some() {
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()),
reason: None,
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)]
#[serde(rename_all = "camelCase")]
pub struct TrustpayRefundRequestCards {

View File

@ -156,7 +156,7 @@ where
&merchant_account,
connector,
&operation,
&payment_data,
&mut payment_data,
&customer,
call_connector_action,
tokenization_action,
@ -399,6 +399,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
.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::DisplayBankTransferInformation { .. } => None,
api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None
})
.ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report()
@ -489,7 +490,7 @@ pub async fn call_connector_service<F, Op, Req>(
merchant_account: &domain::MerchantAccount,
connector: api::ConnectorData,
_operation: &Op,
payment_data: &PaymentData<F>,
payment_data: &mut PaymentData<F>,
customer: &Option<domain::Customer>,
call_connector_action: CallConnectorAction,
tokenization_action: TokenizationAction,
@ -542,6 +543,14 @@ where
)
.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 {
router_data
.decide_flows(
@ -590,7 +599,6 @@ where
for session_connector_data in connectors.iter() {
let connector_id = session_connector_data.connector.connector.id();
let router_data = payment_data
.construct_router_data(state, connector_id, merchant_account, customer)
.await?;
@ -612,12 +620,19 @@ where
let connector_name = session_connector.connector.connector_name.to_string();
match connector_res {
Ok(connector_response) => {
if let Ok(types::PaymentsResponseData::SessionResponse { session_token }) =
if let Ok(types::PaymentsResponseData::SessionResponse { session_token, .. }) =
connector_response.response
{
// 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) => {
logger::error!(
"sessions_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,
connector: &api::ConnectorData,
payment_data: &PaymentData<F>,
router_data: types::RouterData<F, Req, Res>,
router_data: types::RouterData<F, Req, types::PaymentsResponseData>,
should_continue_payment: bool,
) -> RouterResult<(types::RouterData<F, Req, Res>, bool)>
) -> RouterResult<(types::RouterData<F, Req, types::PaymentsResponseData>, bool)>
where
F: Send + Clone + 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>,
{
//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),
},
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),
};
@ -869,7 +894,6 @@ where
.payment_method
.get_required_value("payment_method")?;
let payment_method_type = &payment_data.payment_attempt.payment_method_type;
let is_connector_tokenization_enabled =
is_payment_method_tokenization_enabled_for_connector(
state,

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use api_models::payments as payment_types;
use async_trait::async_trait;
use common_utils::ext_traits::ByteSliceExt;
use error_stack::{report, ResultExt};
use error_stack::{Report, ResultExt};
use super::{ConstructFlowSpecificData, Feature};
use crate::{
@ -10,7 +10,7 @@ use crate::{
errors::{self, ConnectorErrorExt, RouterResult},
payments::{self, access_token, transformers, PaymentData},
},
headers,
headers, logger,
routes::{self, metrics},
services,
types::{self, api, domain},
@ -78,18 +78,22 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio
}
}
fn mk_applepay_session_request(
state: &routes::AppState,
router_data: &types::PaymentsSessionRouterData,
) -> RouterResult<(services::Request, payment_types::ApplepaySessionTokenData)> {
let connector_metadata = router_data.connector_meta_data.clone();
let applepay_metadata = connector_metadata
fn get_applepay_metadata(
connector_metadata: Option<common_utils::pii::SecretSerdeValue>,
) -> RouterResult<payment_types::ApplepaySessionTokenData> {
connector_metadata
.parse_value::<payment_types::ApplepaySessionTokenData>("ApplepaySessionTokenData")
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
field_name: "connector_metadata".to_string(),
expected_format: "applepay_metadata_format".to_string(),
})?;
})
}
fn mk_applepay_session_request(
state: &routes::AppState,
router_data: &types::PaymentsSessionRouterData,
) -> RouterResult<services::Request> {
let applepay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?;
let request = payment_types::ApplepaySessionRequest {
merchant_identifier: applepay_metadata
.data
@ -134,14 +138,10 @@ fn mk_applepay_session_request(
.clone(),
))
.add_certificate_key(Some(
applepay_metadata
.data
.session_token_data
.certificate_keys
.clone(),
applepay_metadata.data.session_token_data.certificate_keys,
))
.build();
Ok((session_request, applepay_metadata))
Ok(session_request)
}
async fn create_applepay_session_token(
@ -149,42 +149,38 @@ 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 delayed_response = connectors_with_delayed_response.contains(&connector_name);
if delayed_response {
let delayed_response_apple_pay_session =
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,
)
}
}?;
} else {
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,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to convert currency to base unit")?,
.change_context(errors::ApiErrorResponse::PreconditionFailed {
message: "Failed to convert currency to base unit".to_string(),
})?,
};
let applepay_payment_request = payment_types::ApplePayPaymentRequest {
@ -206,26 +202,85 @@ async fn create_applepay_session_token(
.data
.payment_request_data
.supported_networks,
merchant_identifier: applepay_metadata
merchant_identifier: Some(
applepay_metadata
.data
.session_token_data
.merchant_identifier,
),
};
let response_router_data = types::PaymentsSessionRouterData {
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: session_response,
payment_request_data: applepay_payment_request,
connector: connector.connector_name.to_string(),
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()
};
Ok(response_router_data)
}),
None => Ok(types::PaymentsSessionRouterData {
response: Ok(types::PaymentsResponseData::SessionResponse {
session_token: payment_types::SessionToken::NoSessionTokenReceived,
}),
..router_data.clone()
}),
}
}
fn create_gpay_session_token(
@ -278,6 +333,18 @@ fn create_gpay_session_token(
Ok(response_router_data)
}
fn log_session_response_if_error(
response: &Result<Result<types::Response, types::Response>, Report<errors::ApiClientError>>,
) {
if let Err(error) = response.as_ref() {
logger::error!(?error);
};
response
.as_ref()
.ok()
.map(|res| res.as_ref().map_err(|error| logger::error!(?error)));
}
impl types::PaymentsSessionRouterData {
pub async fn decide_flow<'a, 'b>(
&'b self,

View File

@ -315,13 +315,28 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>(
types::PaymentsResponseData::PreProcessingResponse {
pre_processing_id,
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 {
status: router_data.status,
payment_method_id: Some(router_data.payment_method_id),
connector_metadata,
preprocessing_step_id: Some(pre_processing_id),
preprocessing_step_id,
connector_transaction_id,
};
(Some(payment_attempt_update), None)
}
types::PaymentsResponseData::TransactionResponse {

View File

@ -377,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),
)
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,
@ -412,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
@ -425,3 +425,11 @@ 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 {
// 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,
&operation,
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>,
operation: &Op,
ephemeral_key_option: Option<ephemeral_key::EphemeralKey>,
session_tokens: Vec<api::SessionToken>,
) -> RouterResponse<api::PaymentsResponse>
where
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 routed_through = payment_attempt.connector.clone();
@ -514,6 +525,34 @@ where
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 {
fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self {
let pi = item.0;
@ -900,6 +939,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
Ok(Self {
email: payment_data.email,
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::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,
@ -216,6 +219,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,
@ -231,6 +236,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::ReceiverDetails,
api_models::payments::AchTransfer,
api_models::payments::ApplePayRedirectData,
api_models::payments::ApplePayThirdPartySdkData,
api_models::payments::GooglePayRedirectData,
api_models::payments::SepaBankTransferInstructions,
api_models::payments::BacsBankTransferInstructions,

View File

@ -270,6 +270,7 @@ pub struct PaymentMethodTokenizationData {
pub struct PaymentsPreProcessingData {
pub email: Option<Email>,
pub currency: Option<storage_enums::Currency>,
pub amount: Option<i64>,
}
#[derive(Debug, Clone)]
@ -425,11 +426,18 @@ pub enum PaymentsResponseData {
related_transaction_id: Option<String>,
},
PreProcessingResponse {
pre_processing_id: String,
pre_processing_id: PreprocessingResponseId,
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)]
pub enum ResponseId {
ConnectorTransactionId(String),

View File

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