Skip to content

Commit

Permalink
feat(payouts): extend routing capabilities to payout operation (#3531)
Browse files Browse the repository at this point in the history
Co-authored-by: Kashif <[email protected]>
  • Loading branch information
kashif-m and kashif-m authored Feb 26, 2024
1 parent c79226b commit 75c633f
Show file tree
Hide file tree
Showing 39 changed files with 1,329 additions and 544 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/validate-openapi-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,6 @@ jobs:
shell: bash
run: |
if ! git diff --quiet --exit-code -- openapi/openapi_spec.json ; then
echo '::error::The OpenAPI spec file is not up-to-date. Please re-generate the OpenAPI spec file using `cargo run --features openapi -- generate-openapi-spec` and commit it.'
echo '::error::The OpenAPI spec file is not up-to-date. Please re-generate the OpenAPI spec file using `cargo run -p openapi` and commit it.'
exit 1
fi
149 changes: 0 additions & 149 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ pub struct MerchantAccountCreate {
/// The routing algorithm to be used for routing payouts to desired connectors
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// A boolean value to indicate if the merchant is a sub-merchant under a master or a parent merchant. By default, its value is false.
Expand Down Expand Up @@ -136,10 +132,6 @@ pub struct MerchantAccountUpdate {
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// A boolean value to indicate if the merchant is a sub-merchant under a master or a parent merchant. By default, its value is false.
Expand Down Expand Up @@ -228,10 +220,6 @@ pub struct MerchantAccountResponse {
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// A boolean value to indicate if the merchant is a sub-merchant under a master or a parent merchant. By default, its value is false.
Expand Down Expand Up @@ -322,124 +310,6 @@ pub struct MerchantDetails {
/// The merchant's address details
pub address: Option<AddressDetails>,
}
#[cfg(feature = "payouts")]
pub mod payout_routing_algorithm {
use std::{fmt, str::FromStr};

use serde::{
de::{self, Visitor},
Deserializer,
};
use serde_json::Map;

use super::PayoutRoutingAlgorithm;
use crate::enums::PayoutConnectors;
struct RoutingAlgorithmVisitor;
struct OptionalRoutingAlgorithmVisitor;

impl<'de> Visitor<'de> for RoutingAlgorithmVisitor {
type Value = serde_json::Value;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("routing algorithm")
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut output = Map::new();
let mut routing_data: String = "".to_string();
let mut routing_type: String = "".to_string();

while let Some(key) = map.next_key()? {
match key {
"type" => {
routing_type = map.next_value()?;
output.insert(
"type".to_string(),
serde_json::Value::String(routing_type.to_owned()),
);
}
"data" => {
routing_data = map.next_value()?;
output.insert(
"data".to_string(),
serde_json::Value::String(routing_data.to_owned()),
);
}
f => {
output.insert(f.to_string(), map.next_value()?);
}
}
}

match routing_type.as_ref() {
"single" => {
let routable_payout_connector = PayoutConnectors::from_str(&routing_data);
let routable_conn = match routable_payout_connector {
Ok(rpc) => Ok(rpc),
Err(_) => Err(de::Error::custom(format!(
"Unknown payout connector {routing_data}"
))),
}?;
Ok(PayoutRoutingAlgorithm::Single(routable_conn))
}
u => Err(de::Error::custom(format!("Unknown routing algorithm {u}"))),
}?;
Ok(serde_json::Value::Object(output))
}
}

impl<'de> Visitor<'de> for OptionalRoutingAlgorithmVisitor {
type Value = Option<serde_json::Value>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("routing algorithm")
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer
.deserialize_any(RoutingAlgorithmVisitor)
.map(Some)
}

fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}

fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
}

#[allow(dead_code)]
pub(crate) fn deserialize<'a, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_any(RoutingAlgorithmVisitor)
}

pub(crate) fn deserialize_option<'a, D>(
deserializer: D,
) -> Result<Option<serde_json::Value>, D::Error>
where
D: Deserializer<'a>,
{
deserializer.deserialize_option(OptionalRoutingAlgorithmVisitor)
}
}

#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct PrimaryBusinessDetails {
Expand Down Expand Up @@ -971,13 +841,6 @@ pub enum PayoutRoutingAlgorithm {
Single(api_enums::PayoutConnectors),
}

#[cfg(feature = "payouts")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum PayoutStraightThroughAlgorithm {
Single(api_enums::PayoutConnectors),
}

#[derive(Clone, Debug, Deserialize, ToSchema, Default, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BusinessProfileCreate {
Expand Down Expand Up @@ -1023,10 +886,6 @@ pub struct BusinessProfileCreate {
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// Verified applepay domains for a particular profile
Expand Down Expand Up @@ -1093,10 +952,6 @@ pub struct BusinessProfileResponse {
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// Verified applepay domains for a particular profile
Expand Down Expand Up @@ -1155,10 +1010,6 @@ pub struct BusinessProfileUpdate {
/// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
#[cfg(feature = "payouts")]
#[schema(value_type = Option<RoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
#[serde(
default,
deserialize_with = "payout_routing_algorithm::deserialize_option"
)]
pub payout_routing_algorithm: Option<serde_json::Value>,

/// Verified applepay domains for a particular profile
Expand Down
22 changes: 22 additions & 0 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,28 @@ impl From<PayoutConnectors> for RoutableConnectors {
}
}

#[cfg(feature = "payouts")]
impl From<PayoutConnectors> for Connector {
fn from(value: PayoutConnectors) -> Self {
match value {
PayoutConnectors::Adyen => Self::Adyen,
PayoutConnectors::Wise => Self::Wise,
}
}
}

#[cfg(feature = "payouts")]
impl TryFrom<Connector> for PayoutConnectors {
type Error = String;
fn try_from(value: Connector) -> Result<Self, Self::Error> {
match value {
Connector::Adyen => Ok(Self::Adyen),
Connector::Wise => Ok(Self::Wise),
_ => Err(format!("Invalid payout connector {}", value)),
}
}
}

#[cfg(feature = "frm")]
#[derive(
Clone,
Expand Down
6 changes: 1 addition & 5 deletions crates/api_models/src/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use masking::Secret;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::{admin, enums as api_enums, payments};
use crate::{enums as api_enums, payments};

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub enum PayoutRequest {
Expand Down Expand Up @@ -48,10 +48,6 @@ pub struct PayoutCreateRequest {
"type": "single",
"data": "adyen"
}))]
#[serde(
default,
deserialize_with = "admin::payout_routing_algorithm::deserialize_option"
)]
pub routing: Option<serde_json::Value>,

/// This allows the merchant to manually select a connector with which the payout can go through
Expand Down
4 changes: 3 additions & 1 deletion crates/api_models/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub use euclid::{
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::enums::{self, RoutableConnectors};
use crate::enums::{self, RoutableConnectors, TransactionType};

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
Expand Down Expand Up @@ -85,6 +85,7 @@ pub struct MerchantRoutingAlgorithm {
pub algorithm: RoutingAlgorithm,
pub created_at: i64,
pub modified_at: i64,
pub algorithm_for: TransactionType,
}

impl EuclidDirFilter for ConnectorSelection {
Expand Down Expand Up @@ -538,6 +539,7 @@ pub struct RoutingDictionaryRecord {
pub description: String,
pub created_at: i64,
pub modified_at: i64,
pub algorithm_for: Option<TransactionType>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
Expand Down
1 change: 1 addition & 0 deletions crates/common_enums/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license.workspace = true
[features]
dummy_connector = []
openapi = []
payouts = []

[dependencies]
diesel = { version = "2.1.0", features = ["postgres"] }
Expand Down
23 changes: 23 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2168,6 +2168,29 @@ pub enum ConnectorStatus {
Active,
}

#[derive(
Clone,
Copy,
Debug,
Eq,
PartialEq,
strum::Display,
strum::EnumString,
serde::Deserialize,
serde::Serialize,
ToSchema,
Default,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum TransactionType {
#[default]
Payment,
#[cfg(feature = "payouts")]
Payout,
}

#[derive(
Clone,
Copy,
Expand Down
2 changes: 1 addition & 1 deletion crates/diesel_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod diesel_exports {
DbRefundStatus as RefundStatus, DbRefundType as RefundType,
DbRequestIncrementalAuthorization as RequestIncrementalAuthorization,
DbRoleScope as RoleScope, DbRoutingAlgorithmKind as RoutingAlgorithmKind,
DbUserStatus as UserStatus,
DbTransactionType as TransactionType, DbUserStatus as UserStatus,
};
}
pub use common_enums::*;
Expand Down
Loading

0 comments on commit 75c633f

Please sign in to comment.