feat(core): support for gpay session token creation (#152)

This commit is contained in:
Narayan Bhat
2022-12-19 13:38:03 +05:30
committed by GitHub
parent 5a43da6a1a
commit 50706bde77
15 changed files with 262 additions and 62 deletions

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

@ -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")?;

View File

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