mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feat(core): support for gpay session token creation (#152)
This commit is contained in:
@ -111,7 +111,7 @@ impl
|
||||
.build(),
|
||||
);
|
||||
|
||||
logger::debug!(session_request=?request);
|
||||
logger::debug!(braintree_session_request=?request);
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
@ -132,9 +132,14 @@ impl
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
_req: &types::PaymentsSessionRouterData,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
Ok(None)
|
||||
let braintree_session_request =
|
||||
utils::Encode::<braintree::BraintreeSessionRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
logger::debug!(?braintree_session_request);
|
||||
Ok(Some(braintree_session_request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
|
||||
@ -19,6 +19,27 @@ pub struct BraintreePaymentsRequest {
|
||||
transaction: TransactionBody,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct BraintreeApiVersion {
|
||||
version: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct BraintreeSessionRequest {
|
||||
client_token: BraintreeApiVersion,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSessionRouterData> for BraintreeSessionRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(_item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
client_token: BraintreeApiVersion {
|
||||
version: "2".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionBody {
|
||||
@ -178,7 +199,7 @@ impl<F, T>
|
||||
Ok(types::RouterData {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: types::api::SessionToken::Paypal {
|
||||
session_token: item.response.client_token.value.authorization_fingerprint,
|
||||
session_token: item.response.client_token.value,
|
||||
},
|
||||
}),
|
||||
..item.data
|
||||
@ -192,16 +213,10 @@ pub struct BraintreePaymentsResponse {
|
||||
transaction: TransactionResponse,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorizationFingerprint {
|
||||
authorization_fingerprint: String,
|
||||
}
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClientToken {
|
||||
#[serde(with = "common_utils::custom_serde::json_string")]
|
||||
pub value: AuthorizationFingerprint,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize)]
|
||||
|
||||
@ -169,7 +169,6 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn payments_core<F, Res, Req, Op, FData>(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
@ -236,8 +235,11 @@ where
|
||||
},
|
||||
)?;
|
||||
|
||||
let connector_data =
|
||||
api::ConnectorData::get_connector_by_name(&state.conf.connectors, &connector)?;
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&connector,
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
|
||||
let flow_type = connector_data
|
||||
.connector
|
||||
@ -402,17 +404,16 @@ where
|
||||
|
||||
for (connector_res, connector) in result.into_iter().zip(connectors) {
|
||||
let connector_name = connector.connector_name.to_string();
|
||||
match connector_res?.response {
|
||||
match connector_res {
|
||||
Ok(connector_response) => {
|
||||
if let types::PaymentsResponseData::SessionResponse { session_token } =
|
||||
connector_response
|
||||
if let Ok(types::PaymentsResponseData::SessionResponse { session_token }) =
|
||||
connector_response.response
|
||||
{
|
||||
payment_data.sessions_token.push(session_token);
|
||||
}
|
||||
}
|
||||
|
||||
Err(connector_error) => {
|
||||
logger::debug!(
|
||||
logger::error!(
|
||||
"sessions_connector_error {} {:?}",
|
||||
connector_name,
|
||||
connector_error
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
use api_models::payments as payment_types;
|
||||
use async_trait::async_trait;
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use super::{ConstructFlowSpecificData, Feature};
|
||||
use crate::{
|
||||
core::{
|
||||
errors::{ConnectorErrorExt, RouterResult},
|
||||
errors::{self, ConnectorErrorExt, RouterResult},
|
||||
payments::{self, transformers, PaymentData},
|
||||
},
|
||||
routes, services,
|
||||
@ -11,6 +13,7 @@ use crate::{
|
||||
self, api,
|
||||
storage::{self, enums},
|
||||
},
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
@ -55,6 +58,45 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio
|
||||
}
|
||||
}
|
||||
|
||||
fn create_gpay_session_token(
|
||||
router_data: &types::PaymentsSessionRouterData,
|
||||
) -> RouterResult<types::PaymentsSessionRouterData> {
|
||||
let connector_metadata = router_data.connector_meta_data.clone();
|
||||
|
||||
let gpay_data = connector_metadata
|
||||
.clone()
|
||||
.parse_value::<payment_types::GpaySessionTokenData>("GpaySessionTokenData")
|
||||
.change_context(errors::ConnectorError::NoConnectorMetaData)
|
||||
.attach_printable(format!(
|
||||
"cannnot parse gpay metadata from the given value {:?}",
|
||||
connector_metadata
|
||||
))
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataFormat {
|
||||
field_name: "connector_metadata".to_string(),
|
||||
expected_format: "gpay_metadata_format".to_string(),
|
||||
})?;
|
||||
|
||||
let session_data = router_data.request.clone();
|
||||
let transaction_info = payment_types::GpayTransactionInfo {
|
||||
country_code: session_data.country.unwrap_or_else(|| "US".to_string()),
|
||||
currency_code: router_data.request.currency.to_string(),
|
||||
total_price_status: "Final".to_string(),
|
||||
total_price: router_data.request.amount,
|
||||
};
|
||||
|
||||
let response_router_data = types::PaymentsSessionRouterData {
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: payment_types::SessionToken::Gpay {
|
||||
allowed_payment_methods: gpay_data.gpay.allowed_payment_methods,
|
||||
transaction_info,
|
||||
},
|
||||
}),
|
||||
..router_data.clone()
|
||||
};
|
||||
|
||||
Ok(response_router_data)
|
||||
}
|
||||
|
||||
impl types::PaymentsSessionRouterData {
|
||||
pub async fn decide_flow<'a, 'b>(
|
||||
&'b self,
|
||||
@ -64,20 +106,25 @@ impl types::PaymentsSessionRouterData {
|
||||
_confirm: Option<bool>,
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
) -> RouterResult<types::PaymentsSessionRouterData> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> = connector.connector.get_connector_integration();
|
||||
let resp = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
self,
|
||||
call_connector_action,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_payment_failed_response())?;
|
||||
match connector.get_token {
|
||||
api::GetToken::Metadata => create_gpay_session_token(self),
|
||||
api::GetToken::Connector => {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> = connector.connector.get_connector_integration();
|
||||
let resp = services::execute_connector_processing_step(
|
||||
state,
|
||||
connector_integration,
|
||||
self,
|
||||
call_connector_action,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_payment_failed_response())?;
|
||||
|
||||
Ok(resp)
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -650,7 +650,11 @@ pub async fn get_connector_default(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||
.as_str();
|
||||
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(connectors, connector_name)?;
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
connector_name,
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
|
||||
Ok(api::ConnectorCallType::Single(connector_data))
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common_utils::ext_traits::ValueExt;
|
||||
use error_stack::ResultExt;
|
||||
use router_derive::PaymentOperation;
|
||||
use router_env::{instrument, tracing};
|
||||
@ -197,6 +198,11 @@ impl<F: Send + Clone> ValidateRequest<F, api::PaymentsSessionRequest> for Paymen
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Default)]
|
||||
pub struct PaymentMethodEnabled {
|
||||
payment_method: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<F: Clone + Send, Op: Send + Sync + Operation<F, api::PaymentsSessionRequest>>
|
||||
Domain<F, api::PaymentsSessionRequest> for Op
|
||||
@ -257,12 +263,13 @@ where
|
||||
|
||||
let supported_connectors: &Vec<String> = state.conf.connectors.supported.wallets.as_ref();
|
||||
|
||||
//FIXME: Check if merchant has enabled wallet through the connector
|
||||
let connector_names = db
|
||||
let connector_accounts = db
|
||||
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Database error when querying for merchant accounts")?
|
||||
.attach_printable("Database error when querying for merchant connector accounts")?;
|
||||
|
||||
let normal_connector_names = connector_accounts
|
||||
.iter()
|
||||
.filter(|connector_account| {
|
||||
supported_connectors.contains(&connector_account.connector_name)
|
||||
@ -270,11 +277,48 @@ where
|
||||
.map(|filtered_connector| filtered_connector.connector_name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut connectors_data = Vec::with_capacity(connector_names.len());
|
||||
// Parse the payment methods enabled to check if the merchant has enabled gpay ( wallet )
|
||||
// through that connector this parsing from Value to payment method is costly and has to be done for every connector
|
||||
// for sure looks like an area of optimization
|
||||
let session_token_from_metadata_connectors = connector_accounts
|
||||
.iter()
|
||||
.filter(|connector_account| {
|
||||
connector_account
|
||||
.payment_methods_enabled
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|payment_method| {
|
||||
let parsed_payment_method: PaymentMethodEnabled = payment_method
|
||||
.clone()
|
||||
.parse_value("payment_method")
|
||||
.unwrap_or_default();
|
||||
|
||||
for connector_name in connector_names {
|
||||
let connector_data =
|
||||
api::ConnectorData::get_connector_by_name(connectors, &connector_name)?;
|
||||
parsed_payment_method.payment_method == "wallet"
|
||||
})
|
||||
})
|
||||
.map(|filtered_connector| filtered_connector.connector_name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut connectors_data = Vec::with_capacity(
|
||||
normal_connector_names.len() + session_token_from_metadata_connectors.len(),
|
||||
);
|
||||
|
||||
for connector_name in normal_connector_names {
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
&connector_name,
|
||||
api::GetToken::Connector,
|
||||
)?;
|
||||
connectors_data.push(connector_data);
|
||||
}
|
||||
|
||||
for connector_name in session_token_from_metadata_connectors {
|
||||
let connector_data = api::ConnectorData::get_connector_by_name(
|
||||
connectors,
|
||||
&connector_name,
|
||||
api::GetToken::Metadata,
|
||||
)?;
|
||||
connectors_data.push(connector_data);
|
||||
}
|
||||
|
||||
|
||||
@ -468,6 +468,11 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::PaymentsSessionData {
|
||||
Ok(Self {
|
||||
amount: payment_data.amount.into(),
|
||||
currency: payment_data.currency,
|
||||
country: payment_data
|
||||
.address
|
||||
.billing
|
||||
.and_then(|billing_address| billing_address.address.map(|address| address.country))
|
||||
.flatten(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,10 +97,13 @@ pub async fn trigger_refund_to_gateway(
|
||||
.clone()
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
|
||||
let connector_id = connector.to_string();
|
||||
let connector: api::ConnectorData =
|
||||
api::ConnectorData::get_connector_by_name(&state.conf.connectors, &connector_id)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get the connector")?;
|
||||
let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&connector_id,
|
||||
api::GetToken::Connector,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get the connector")?;
|
||||
|
||||
let currency = payment_attempt.currency.ok_or_else(|| {
|
||||
report!(errors::ApiErrorResponse::MissingRequiredField {
|
||||
@ -241,10 +244,13 @@ pub async fn sync_refund_with_gateway(
|
||||
refund: &storage::Refund,
|
||||
) -> RouterResult<storage::Refund> {
|
||||
let connector_id = refund.connector.to_string();
|
||||
let connector: api::ConnectorData =
|
||||
api::ConnectorData::get_connector_by_name(&state.conf.connectors, &connector_id)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get the connector")?;
|
||||
let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
&connector_id,
|
||||
api::GetToken::Connector,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get the connector")?;
|
||||
|
||||
let currency = payment_attempt.currency.get_required_value("currency")?;
|
||||
|
||||
|
||||
@ -197,10 +197,13 @@ pub async fn webhooks_core(
|
||||
connector_name: &str,
|
||||
body: actix_web::web::Bytes,
|
||||
) -> RouterResponse<serde_json::Value> {
|
||||
let connector =
|
||||
api::ConnectorData::get_connector_by_name(&state.conf.connectors, connector_name)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed construction of ConnectorData")?;
|
||||
let connector = api::ConnectorData::get_connector_by_name(
|
||||
&state.conf.connectors,
|
||||
connector_name,
|
||||
api::GetToken::Connector,
|
||||
)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed construction of ConnectorData")?;
|
||||
|
||||
let connector = connector.connector;
|
||||
|
||||
|
||||
@ -124,9 +124,9 @@ pub struct PaymentsCancelData {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsSessionData {
|
||||
//TODO: Add the fields here as required
|
||||
pub amount: i64,
|
||||
pub currency: storage_enums::Currency,
|
||||
pub country: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -147,12 +147,6 @@ pub struct PaymentsTransactionResponse {
|
||||
pub redirect: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsSessionResponse {
|
||||
pub session_id: Option<String>,
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PaymentsResponseData {
|
||||
TransactionResponse {
|
||||
|
||||
@ -64,9 +64,17 @@ impl<T: Refund + Payment + Debug + ConnectorRedirectResponse + Send + IncomingWe
|
||||
|
||||
type BoxedConnector = Box<&'static (dyn Connector + marker::Sync)>;
|
||||
|
||||
// Normal flow will call the connector and follow the flow specific operations (capture, authorize)
|
||||
// SessionTokenFromMetadata will avoid calling the connector instead create the session token ( for sdk )
|
||||
pub enum GetToken {
|
||||
Metadata,
|
||||
Connector,
|
||||
}
|
||||
|
||||
pub struct ConnectorData {
|
||||
pub connector: BoxedConnector,
|
||||
pub connector_name: types::Connector,
|
||||
pub get_token: GetToken,
|
||||
}
|
||||
|
||||
pub enum ConnectorCallType {
|
||||
@ -78,6 +86,7 @@ impl ConnectorData {
|
||||
pub fn get_connector_by_name(
|
||||
connectors: &Connectors,
|
||||
name: &str,
|
||||
connector_type: GetToken,
|
||||
) -> CustomResult<ConnectorData, errors::ApiErrorResponse> {
|
||||
let connector = Self::convert_connector(connectors, name)?;
|
||||
let connector_name = types::Connector::from_str(name)
|
||||
@ -88,6 +97,7 @@ impl ConnectorData {
|
||||
Ok(ConnectorData {
|
||||
connector,
|
||||
connector_name,
|
||||
get_token: connector_type,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user