Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/juspay/hyperswitch into pay…
Browse files Browse the repository at this point in the history
…one-payout-flows
  • Loading branch information
KiranKBR committed May 13, 2024
2 parents d9c8711 + 2a302eb commit 82c8fe4
Show file tree
Hide file tree
Showing 144 changed files with 5,379 additions and 2,352 deletions.
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,51 @@ All notable changes to HyperSwitch will be documented here.

- - -

## 2024.05.13.0

### Features

- **Connectors:** Add mandate validation for auth flow ([#4089](https://github.com/juspay/hyperswitch/pull/4089)) ([`fef28c3`](https://github.com/juspay/hyperswitch/commit/fef28c3345ae60046f46a2bdf9eca6b38278d75b))
- **analytics:** Authentication analytics ([#4429](https://github.com/juspay/hyperswitch/pull/4429)) ([`24d1542`](https://github.com/juspay/hyperswitch/commit/24d154248c8814e729206208f096aba68dcff8c0))

### Bug Fixes

- **connector:** [BOA/CYBS] add cancelled status to refund response ([#4620](https://github.com/juspay/hyperswitch/pull/4620)) ([`cf0e3da`](https://github.com/juspay/hyperswitch/commit/cf0e3daeaa1dfdfa00d4cccdff5b845ac368bcb9))
- **router:** Fix QR data into image conversion ([#4619](https://github.com/juspay/hyperswitch/pull/4619)) ([`28ab368`](https://github.com/juspay/hyperswitch/commit/28ab36873b2a475f1de95819b3d81aae954a2cfc))

### Refactors

- **payment_method_data:** Send optional billing details in response ([#4569](https://github.com/juspay/hyperswitch/pull/4569)) ([`86e0550`](https://github.com/juspay/hyperswitch/commit/86e05501cbea53fd85e2bc67a1c2be4cba47d0ff))

**Full Changelog:** [`2024.05.10.0...2024.05.13.0`](https://github.com/juspay/hyperswitch/compare/2024.05.10.0...2024.05.13.0)

- - -

## 2024.05.10.0

### Features

- **connector:** [Payone] add connector template code ([#4469](https://github.com/juspay/hyperswitch/pull/4469)) ([`f386f42`](https://github.com/juspay/hyperswitch/commit/f386f423c0e5fac55a24756d7ee7a3ce1c20fb13))
- **users:**
- Create API to Verify TOTP ([#4597](https://github.com/juspay/hyperswitch/pull/4597)) ([`9135423`](https://github.com/juspay/hyperswitch/commit/91354232e03a8dbd9ad9eccc8620eac321765dd7))
- New routes to accept invite and list merchants ([#4591](https://github.com/juspay/hyperswitch/pull/4591)) ([`e70d58a`](https://github.com/juspay/hyperswitch/commit/e70d58afc941d436aae0aaa683c2e8b5db2ade33))

### Bug Fixes

- **connector:**
- [iatapay]handle empty error response in case of 401 ([#4291](https://github.com/juspay/hyperswitch/pull/4291)) ([`d1404d9`](https://github.com/juspay/hyperswitch/commit/d1404d9aff2aea513a2ffd422c7e10e760b7382c))
- [BAMBORA] Audit Fixes for Bambora ([#4604](https://github.com/juspay/hyperswitch/pull/4604)) ([`366596f`](https://github.com/juspay/hyperswitch/commit/366596f14d6c874a8e2d418a99beb90046c5b040))
- **router:** [NETCETERA] skip sending browser_information in authentication request for app device_channel ([#4613](https://github.com/juspay/hyperswitch/pull/4613)) ([`d2a496c`](https://github.com/juspay/hyperswitch/commit/d2a496cf4ddab94efa5ad1127a94687d45bed027))
- **users:** Fix bugs caused by the new token only flows ([#4607](https://github.com/juspay/hyperswitch/pull/4607)) ([`a0f11d7`](https://github.com/juspay/hyperswitch/commit/a0f11d79add17e0bc19d8677c90f8a35d6c99c97))

### Refactors

- **billing:** Store `payment_method_data_billing` for recurring payments ([#4513](https://github.com/juspay/hyperswitch/pull/4513)) ([`55ae0fc`](https://github.com/juspay/hyperswitch/commit/55ae0fc5f704d8b35815fcd2170befb4a726ea8d))

**Full Changelog:** [`2024.05.09.0...2024.05.10.0`](https://github.com/juspay/hyperswitch/compare/2024.05.09.0...2024.05.10.0)

- - -

## 2024.05.09.0

### Features
Expand Down
2 changes: 2 additions & 0 deletions config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ forte.base_url = "https://sandbox.forte.net/api/v3"
globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/"
globepay.base_url = "https://pay.globepay.co/"
gocardless.base_url = "https://api-sandbox.gocardless.com"
gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net"
helcim.base_url = "https://api.helcim.com/"
iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1"
klarna.base_url = "https://api-na.playground.klarna.com/"
Expand Down Expand Up @@ -275,6 +276,7 @@ cards = [
"globalpay",
"globepay",
"gocardless",
"gpayments",
"helcim",
"mollie",
"paypal",
Expand Down
2 changes: 2 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ cards = [
"globalpay",
"globepay",
"gocardless",
"gpayments",
"helcim",
"iatapay",
"mollie",
Expand Down Expand Up @@ -192,6 +193,7 @@ forte.base_url = "https://sandbox.forte.net/api/v3"
globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/"
globepay.base_url = "https://pay.globepay.co/"
gocardless.base_url = "https://api-sandbox.gocardless.com"
gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net"
helcim.base_url = "https://api.helcim.com/"
iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1"
klarna.base_url = "https://api-na.playground.klarna.com/"
Expand Down
2 changes: 2 additions & 0 deletions config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ forte.base_url = "https://sandbox.forte.net/api/v3"
globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/"
globepay.base_url = "https://pay.globepay.co/"
gocardless.base_url = "https://api-sandbox.gocardless.com"
gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net"
helcim.base_url = "https://api.helcim.com/"
iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1"
klarna.base_url = "https://api-na.playground.klarna.com/"
Expand Down Expand Up @@ -204,6 +205,7 @@ cards = [
"globalpay",
"globepay",
"gocardless",
"gpayments",
"helcim",
"iatapay",
"mollie",
Expand Down
6 changes: 6 additions & 0 deletions crates/analytics/src/auth_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod accumulator;
mod core;
pub mod metrics;
pub use accumulator::{AuthEventMetricAccumulator, AuthEventMetricsAccumulator};

pub use self::core::get_metrics;
58 changes: 58 additions & 0 deletions crates/analytics/src/auth_events/accumulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use api_models::analytics::auth_events::AuthEventMetricsBucketValue;

use super::metrics::AuthEventMetricRow;

#[derive(Debug, Default)]
pub struct AuthEventMetricsAccumulator {
pub three_ds_sdk_count: CountAccumulator,
pub authentication_attempt_count: CountAccumulator,
pub authentication_success_count: CountAccumulator,
pub challenge_flow_count: CountAccumulator,
pub challenge_attempt_count: CountAccumulator,
pub challenge_success_count: CountAccumulator,
pub frictionless_flow_count: CountAccumulator,
}

#[derive(Debug, Default)]
#[repr(transparent)]
pub struct CountAccumulator {
pub count: Option<i64>,
}

pub trait AuthEventMetricAccumulator {
type MetricOutput;

fn add_metrics_bucket(&mut self, metrics: &AuthEventMetricRow);

fn collect(self) -> Self::MetricOutput;
}

impl AuthEventMetricAccumulator for CountAccumulator {
type MetricOutput = Option<u64>;
#[inline]
fn add_metrics_bucket(&mut self, metrics: &AuthEventMetricRow) {
self.count = match (self.count, metrics.count) {
(None, None) => None,
(None, i @ Some(_)) | (i @ Some(_), None) => i,
(Some(a), Some(b)) => Some(a + b),
}
}
#[inline]
fn collect(self) -> Self::MetricOutput {
self.count.and_then(|i| u64::try_from(i).ok())
}
}

impl AuthEventMetricsAccumulator {
pub fn collect(self) -> AuthEventMetricsBucketValue {
AuthEventMetricsBucketValue {
three_ds_sdk_count: self.three_ds_sdk_count.collect(),
authentication_attempt_count: self.authentication_attempt_count.collect(),
authentication_success_count: self.authentication_success_count.collect(),
challenge_flow_count: self.challenge_flow_count.collect(),
challenge_attempt_count: self.challenge_attempt_count.collect(),
challenge_success_count: self.challenge_success_count.collect(),
frictionless_flow_count: self.frictionless_flow_count.collect(),
}
}
}
108 changes: 108 additions & 0 deletions crates/analytics/src/auth_events/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::collections::HashMap;

use api_models::analytics::{
auth_events::{AuthEventMetrics, AuthEventMetricsBucketIdentifier, MetricsBucketResponse},
AnalyticsMetadata, GetAuthEventMetricRequest, MetricsResponse,
};
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};

use super::AuthEventMetricsAccumulator;
use crate::{
auth_events::AuthEventMetricAccumulator,
errors::{AnalyticsError, AnalyticsResult},
AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn get_metrics(
pool: &AnalyticsProvider,
merchant_id: &String,
publishable_key: Option<&String>,
req: GetAuthEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
let mut metrics_accumulator: HashMap<
AuthEventMetricsBucketIdentifier,
AuthEventMetricsAccumulator,
> = HashMap::new();

if let Some(publishable_key) = publishable_key {
let mut set = tokio::task::JoinSet::new();
for metric_type in req.metrics.iter().cloned() {
let req = req.clone();
let merchant_id_scoped = merchant_id.to_owned();
let publishable_key_scoped = publishable_key.to_owned();
let pool = pool.clone();
set.spawn(async move {
let data = pool
.get_auth_event_metrics(
&metric_type,
&merchant_id_scoped,
&publishable_key_scoped,
&req.time_series.map(|t| t.granularity),
&req.time_range,
)
.await
.change_context(AnalyticsError::UnknownError);
(metric_type, data)
});
}

while let Some((metric, data)) = set
.join_next()
.await
.transpose()
.change_context(AnalyticsError::UnknownError)?
{
for (id, value) in data? {
let metrics_builder = metrics_accumulator.entry(id).or_default();
match metric {
AuthEventMetrics::ThreeDsSdkCount => metrics_builder
.three_ds_sdk_count
.add_metrics_bucket(&value),
AuthEventMetrics::AuthenticationAttemptCount => metrics_builder
.authentication_attempt_count
.add_metrics_bucket(&value),
AuthEventMetrics::AuthenticationSuccessCount => metrics_builder
.authentication_success_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeFlowCount => metrics_builder
.challenge_flow_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeAttemptCount => metrics_builder
.challenge_attempt_count
.add_metrics_bucket(&value),
AuthEventMetrics::ChallengeSuccessCount => metrics_builder
.challenge_success_count
.add_metrics_bucket(&value),
AuthEventMetrics::FrictionlessFlowCount => metrics_builder
.frictionless_flow_count
.add_metrics_bucket(&value),
}
}
}

let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
.into_iter()
.map(|(id, val)| MetricsBucketResponse {
values: val.collect(),
dimensions: id,
})
.collect();

Ok(MetricsResponse {
query_data,
meta_data: [AnalyticsMetadata {
current_time_range: req.time_range,
}],
})
} else {
logger::error!("Publishable key not present for merchant ID");
Ok(MetricsResponse {
query_data: vec![],
meta_data: [AnalyticsMetadata {
current_time_range: req.time_range,
}],
})
}
}
107 changes: 107 additions & 0 deletions crates/analytics/src/auth_events/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use api_models::analytics::{
auth_events::{AuthEventMetrics, AuthEventMetricsBucketIdentifier},
Granularity, TimeRange,
};
use time::PrimitiveDateTime;

use crate::{
query::{Aggregate, GroupByClause, ToSql, Window},
types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, MetricsResult},
};

mod authentication_attempt_count;
mod authentication_success_count;
mod challenge_attempt_count;
mod challenge_flow_count;
mod challenge_success_count;
mod frictionless_flow_count;
mod three_ds_sdk_count;

use authentication_attempt_count::AuthenticationAttemptCount;
use authentication_success_count::AuthenticationSuccessCount;
use challenge_attempt_count::ChallengeAttemptCount;
use challenge_flow_count::ChallengeFlowCount;
use challenge_success_count::ChallengeSuccessCount;
use frictionless_flow_count::FrictionlessFlowCount;
use three_ds_sdk_count::ThreeDsSdkCount;

#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub struct AuthEventMetricRow {
pub count: Option<i64>,
pub time_bucket: Option<String>,
}

pub trait AuthEventMetricAnalytics: LoadRow<AuthEventMetricRow> {}

#[async_trait::async_trait]
pub trait AuthEventMetric<T>
where
T: AnalyticsDataSource + AuthEventMetricAnalytics,
{
async fn load_metrics(
&self,
merchant_id: &str,
publishable_key: &str,
granularity: &Option<Granularity>,
time_range: &TimeRange,
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>>;
}

#[async_trait::async_trait]
impl<T> AuthEventMetric<T> for AuthEventMetrics
where
T: AnalyticsDataSource + AuthEventMetricAnalytics,
PrimitiveDateTime: ToSql<T>,
AnalyticsCollection: ToSql<T>,
Granularity: GroupByClause<T>,
Aggregate<&'static str>: ToSql<T>,
Window<&'static str>: ToSql<T>,
{
async fn load_metrics(
&self,
merchant_id: &str,
publishable_key: &str,
granularity: &Option<Granularity>,
time_range: &TimeRange,
pool: &T,
) -> MetricsResult<Vec<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
match self {
Self::ThreeDsSdkCount => {
ThreeDsSdkCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::AuthenticationAttemptCount => {
AuthenticationAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::AuthenticationSuccessCount => {
AuthenticationSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeFlowCount => {
ChallengeFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeAttemptCount => {
ChallengeAttemptCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::ChallengeSuccessCount => {
ChallengeSuccessCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
Self::FrictionlessFlowCount => {
FrictionlessFlowCount
.load_metrics(merchant_id, publishable_key, granularity, time_range, pool)
.await
}
}
}
}

0 comments on commit 82c8fe4

Please sign in to comment.