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

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

View File

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

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;

View File

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

View File

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