Skip to content

Commit

Permalink
feat(payouts): implement list and filter APIs (juspay#3651)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Kashif <[email protected]>
  • Loading branch information
3 people committed Mar 21, 2024
1 parent 4fb7c6e commit fb5f0e6
Show file tree
Hide file tree
Showing 26 changed files with 1,615 additions and 25 deletions.
27 changes: 26 additions & 1 deletion crates/api_models/src/events/payouts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::payouts::{
PayoutActionRequest, PayoutCreateRequest, PayoutCreateResponse, PayoutRetrieveRequest,
PayoutActionRequest, PayoutCreateRequest, PayoutCreateResponse, PayoutListConstraints,
PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, PayoutRetrieveRequest,
};

impl ApiEventMetric for PayoutRetrieveRequest {
Expand All @@ -27,3 +28,27 @@ impl ApiEventMetric for PayoutActionRequest {
Some(ApiEventsType::Payout)
}
}

impl ApiEventMetric for PayoutListConstraints {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payout)
}
}

impl ApiEventMetric for PayoutListFilterConstraints {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payout)
}
}

impl ApiEventMetric for PayoutListResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payout)
}
}

impl ApiEventMetric for PayoutListFilters {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payout)
}
}
9 changes: 3 additions & 6 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{collections::HashMap, fmt, num::NonZeroI64};

use cards::CardNumber;
use common_utils::{
consts::default_payments_list_limit,
crypto,
ext_traits::Encode,
pii::{self, Email},
Expand Down Expand Up @@ -3197,7 +3198,7 @@ pub struct PaymentListConstraints {

/// limit on the number of objects to return
#[schema(default = 10, maximum = 100)]
#[serde(default = "default_limit")]
#[serde(default = "default_payments_list_limit")]
pub limit: u32,

/// The time at which payment is created
Expand Down Expand Up @@ -3283,7 +3284,7 @@ pub struct PaymentListFilterConstraints {
/// The identifier for customer
pub customer_id: Option<String>,
/// The limit on the number of objects. The default limit is 10 and max limit is 20
#[serde(default = "default_limit")]
#[serde(default = "default_payments_list_limit")]
pub limit: u32,
/// The starting point within a list of objects
pub offset: Option<u32>,
Expand Down Expand Up @@ -3353,10 +3354,6 @@ pub struct VerifyResponse {
pub error_message: Option<String>,
}

fn default_limit() -> u32 {
10
}

#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
pub struct PaymentsRedirectionResponse {
pub redirect_url: String,
Expand Down
143 changes: 143 additions & 0 deletions crates/api_models/src/payouts.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use cards::CardNumber;
use common_utils::{
consts::default_payouts_list_limit,
crypto,
pii::{self, Email},
};
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

use crate::{enums as api_enums, payments};
Expand Down Expand Up @@ -393,6 +395,54 @@ pub struct PayoutCreateResponse {

/// The business profile that is associated with this payment
pub profile_id: String,

/// Time when the payout was created
#[schema(example = "2022-09-10T10:11:12Z")]
#[serde(with = "common_utils::custom_serde::iso8601::option")]
pub created: Option<PrimitiveDateTime>,

/// List of attempts
#[schema(value_type = Option<Vec<PayoutAttemptResponse>>)]
#[serde(skip_serializing_if = "Option::is_none")]
pub attempts: Option<Vec<PayoutAttemptResponse>>,
}

#[derive(
Default, Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema,
)]
pub struct PayoutAttemptResponse {
/// Unique identifier for the attempt
pub attempt_id: String,
/// The status of the attempt
#[schema(value_type = PayoutStatus, example = "failed")]
pub status: api_enums::PayoutStatus,
/// The payout attempt amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
pub amount: i64,
/// The currency of the amount of the payout attempt
#[schema(value_type = Option<Currency>, example = "USD")]
pub currency: Option<api_enums::Currency>,
/// The connector used for the payout
pub connector: Option<String>,
/// Connector's error code in case of failures
pub error_code: Option<String>,
/// Connector's error message in case of failures
pub error_message: Option<String>,
/// The payout method that was used
#[schema(value_type = Option<PayoutType>, example = "bank")]
pub payment_method: Option<api_enums::PayoutType>,
/// Payment Method Type
#[schema(value_type = Option<PaymentMethodType>, example = "bacs")]
pub payout_method_type: Option<api_enums::PaymentMethodType>,
/// A unique identifier for a payout provided by the connector
pub connector_transaction_id: Option<String>,
/// If the payout was cancelled the reason provided here
pub cancellation_reason: Option<String>,
/// Provide a reference to a stored payout method
pub payout_token: Option<String>,
/// error code unified across the connectors is received here if there was an error while calling connector
pub unified_code: Option<String>,
/// error message unified across the connectors is received here if there was an error while calling connector
pub unified_message: Option<String>,
}

#[derive(Default, Debug, Clone, Deserialize, ToSchema)]
Expand Down Expand Up @@ -430,3 +480,96 @@ pub struct PayoutActionRequest {
)]
pub payout_id: String,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct PayoutListConstraints {
/// The identifier for customer
#[schema(example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
pub customer_id: Option<String>,

/// A cursor for use in pagination, fetch the next list after some object
#[schema(example = "pay_fafa124123")]
pub starting_after: Option<String>,

/// A cursor for use in pagination, fetch the previous list before some object
#[schema(example = "pay_fafa124123")]
pub ending_before: Option<String>,

/// limit on the number of objects to return
#[schema(default = 10, maximum = 100)]
#[serde(default = "default_payouts_list_limit")]
pub limit: u32,

/// The time at which payout is created
#[schema(example = "2022-09-10T10:11:12Z")]
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub created: Option<PrimitiveDateTime>,

/// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
#[serde(flatten)]
#[schema(value_type = Option<TimeRange>)]
pub time_range: Option<payments::TimeRange>,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct PayoutListFilterConstraints {
/// The identifier for payout
#[schema(
value_type = Option<String>,
min_length = 30,
max_length = 30,
example = "payout_mbabizu24mvu3mela5njyhpit4"
)]
pub payout_id: Option<String>,
/// The identifier for business profile
pub profile_id: Option<String>,
/// The identifier for customer
#[schema(example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
pub customer_id: Option<String>,
/// The limit on the number of objects. The default limit is 10 and max limit is 20
#[serde(default = "default_payouts_list_limit")]
pub limit: u32,
/// The starting point within a list of objects
pub offset: Option<u32>,
/// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
#[serde(flatten)]
#[schema(value_type = Option<TimeRange>)]
pub time_range: Option<payments::TimeRange>,
/// The list of connectors to filter payouts list
#[schema(value_type = Option<Vec<PayoutConnectors>>, max_length = 255, example = json!(["wise", "adyen"]))]
pub connector: Option<Vec<api_enums::PayoutConnectors>>,
/// The list of currencies to filter payouts list
#[schema(value_type = Currency, example = "USD")]
pub currency: Option<Vec<api_enums::Currency>>,
/// The list of payout status to filter payouts list
#[schema(value_type = Option<Vec<PayoutStatus>>, example = json!(["pending", "failed"]))]
pub status: Option<Vec<api_enums::PayoutStatus>>,
/// The list of payout methods to filter payouts list
#[schema(value_type = Option<Vec<PayoutType>>, example = json!(["bank", "card"]))]
pub payout_method: Option<Vec<common_enums::PayoutType>>,
/// Type of recipient
#[schema(value_type = PayoutEntityType, example = "Individual")]
pub entity_type: Option<common_enums::PayoutEntityType>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct PayoutListResponse {
/// The number of payouts included in the list
pub size: usize,
// The list of payouts response objects
pub data: Vec<PayoutCreateResponse>,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PayoutListFilters {
/// The list of available connector filters
pub connector: Vec<api_enums::PayoutConnectors>,
/// The list of available currency filters
pub currency: Vec<common_enums::Currency>,
/// The list of available payment status filters
pub status: Vec<common_enums::PayoutStatus>,
/// The list of available payment method filters
pub payout_method: Vec<common_enums::PayoutType>,
}
1 change: 1 addition & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,7 @@ pub enum CanadaStatesAbbreviation {
Debug,
Default,
Eq,
Hash,
PartialEq,
ToSchema,
serde::Deserialize,
Expand Down
13 changes: 13 additions & 0 deletions crates/common_utils/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ pub static FRM_CONFIGS_EG: &str = r#"
pub const PAYMENTS_LIST_MAX_LIMIT_V1: u32 = 100;
/// Maximum limit for payments list post api with filters
pub const PAYMENTS_LIST_MAX_LIMIT_V2: u32 = 20;
/// Default limit for payments list API
pub fn default_payments_list_limit() -> u32 {
10
}

/// Maximum limit for payment link list get api
pub const PAYMENTS_LINK_LIST_LIMIT: u32 = 100;

/// Maximum limit for payouts list get api
pub const PAYOUTS_LIST_MAX_LIMIT_GET: u32 = 100;
/// Maximum limit for payouts list post api
pub const PAYOUTS_LIST_MAX_LIMIT_POST: u32 = 20;
/// Default limit for payouts list API
pub fn default_payouts_list_limit() -> u32 {
10
}

/// surcharge percentage maximum precision length
pub const SURCHARGE_PERCENTAGE_PRECISION_LENGTH: u8 = 2;

Expand Down
97 changes: 97 additions & 0 deletions crates/data_models/src/payouts.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,100 @@
pub mod payout_attempt;
#[allow(clippy::module_inception)]
pub mod payouts;

use common_enums as storage_enums;
use common_utils::consts;
use time::PrimitiveDateTime;

pub enum PayoutFetchConstraints {
Single { payout_id: String },
List(Box<PayoutListParams>),
}

pub struct PayoutListParams {
pub offset: u32,
pub starting_at: Option<PrimitiveDateTime>,
pub ending_at: Option<PrimitiveDateTime>,
pub connector: Option<Vec<api_models::enums::PayoutConnectors>>,
pub currency: Option<Vec<storage_enums::Currency>>,
pub status: Option<Vec<storage_enums::PayoutStatus>>,
pub payout_method: Option<Vec<common_enums::PayoutType>>,
pub profile_id: Option<String>,
pub customer_id: Option<String>,
pub starting_after_id: Option<String>,
pub ending_before_id: Option<String>,
pub entity_type: Option<common_enums::PayoutEntityType>,
pub limit: Option<u32>,
}

impl From<api_models::payouts::PayoutListConstraints> for PayoutFetchConstraints {
fn from(value: api_models::payouts::PayoutListConstraints) -> Self {
Self::List(Box::new(PayoutListParams {
offset: 0,
starting_at: value
.time_range
.map_or(value.created, |t| Some(t.start_time)),
ending_at: value.time_range.and_then(|t| t.end_time),
connector: None,
currency: None,
status: None,
payout_method: None,
profile_id: None,
customer_id: value.customer_id,
starting_after_id: value.starting_after,
ending_before_id: value.ending_before,
entity_type: None,
limit: Some(std::cmp::min(
value.limit,
consts::PAYOUTS_LIST_MAX_LIMIT_GET,
)),
}))
}
}

impl From<api_models::payments::TimeRange> for PayoutFetchConstraints {
fn from(value: api_models::payments::TimeRange) -> Self {
Self::List(Box::new(PayoutListParams {
offset: 0,
starting_at: Some(value.start_time),
ending_at: value.end_time,
connector: None,
currency: None,
status: None,
payout_method: None,
profile_id: None,
customer_id: None,
starting_after_id: None,
ending_before_id: None,
entity_type: None,
limit: None,
}))
}
}

impl From<api_models::payouts::PayoutListFilterConstraints> for PayoutFetchConstraints {
fn from(value: api_models::payouts::PayoutListFilterConstraints) -> Self {
if let Some(payout_id) = value.payout_id {
Self::Single { payout_id }
} else {
Self::List(Box::new(PayoutListParams {
offset: value.offset.unwrap_or_default(),
starting_at: value.time_range.map(|t| t.start_time),
ending_at: value.time_range.and_then(|t| t.end_time),
connector: value.connector,
currency: value.currency,
status: value.status,
payout_method: value.payout_method,
profile_id: value.profile_id,
customer_id: value.customer_id,
starting_after_id: None,
ending_before_id: None,
entity_type: value.entity_type,
limit: Some(std::cmp::min(
value.limit,
consts::PAYOUTS_LIST_MAX_LIMIT_POST,
)),
}))
}
}
}
Loading

0 comments on commit fb5f0e6

Please sign in to comment.