Skip to content

Commit

Permalink
feat(payment_charges): add support for collecting and refunding charg…
Browse files Browse the repository at this point in the history
…es on payments (#4628)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Narayan Bhat <[email protected]>
Co-authored-by: Shankar Singh C <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: YongJoon Kim <[email protected]>
Co-authored-by: Sanchith Hegde <[email protected]>
Co-authored-by: chikke srujan <[email protected]>
Co-authored-by: Hrithikesh <[email protected]>
Co-authored-by: Chethan Rao <[email protected]>
Co-authored-by: Sampras Lopes <[email protected]>
  • Loading branch information
11 people authored May 24, 2024
1 parent a7fc4c6 commit 55ccce6
Show file tree
Hide file tree
Showing 106 changed files with 882 additions and 22 deletions.
3 changes: 2 additions & 1 deletion connector-template/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ impl<F,T> TryFrom<types::ResponseRouterData<F, {{project-name | downcase | pasca
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: None,
incremental_authorization_allowed: None,
incremental_authorization_allowed: None,
charge_id: None,
}),
..item.data
})
Expand Down
44 changes: 44 additions & 0 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,50 @@ pub fn convert_authentication_connector(connector_name: &str) -> Option<Authenti
AuthenticationConnectors::from_str(connector_name).ok()
}

#[derive(
Clone,
Debug,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumString,
ToSchema,
Hash,
)]
pub enum PaymentChargeType {
#[serde(untagged)]
Stripe(StripeChargeType),
}

impl Default for PaymentChargeType {
fn default() -> Self {
Self::Stripe(StripeChargeType::default())
}
}

#[derive(
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
ToSchema,
serde::Serialize,
serde::Deserialize,
strum::Display,
strum::EnumString,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum StripeChargeType {
#[default]
Direct,
Destination,
}

#[cfg(feature = "frm")]
pub fn convert_frm_connector(connector_name: &str) -> Option<FrmConnectors> {
FrmConnectors::from_str(connector_name).ok()
Expand Down
36 changes: 36 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,23 @@ pub struct PaymentsRequest {

/// Details required for recurring payment
pub recurring_details: Option<RecurringDetails>,

/// Fee information to be charged on the payment being collected
pub charges: Option<PaymentChargeRequest>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct PaymentChargeRequest {
/// Stripe's charge type
#[schema(value_type = PaymentChargeType, example = "direct")]
pub charge_type: api_enums::PaymentChargeType,

/// Platform fees to be collected on the payment
pub fees: i64,

/// Identifier for the reseller's account to send the funds to
pub transfer_account_id: String,
}

impl PaymentsRequest {
Expand Down Expand Up @@ -3426,11 +3443,30 @@ pub struct PaymentsResponse {
#[serde(default, with = "common_utils::custom_serde::iso8601::option")]
pub updated: Option<PrimitiveDateTime>,

/// Fee information to be charged on the payment being collected
pub charges: Option<PaymentChargeResponse>,

/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.
#[schema(value_type = Option<Object>, example = r#"{ "fulfillment_method" : "deliver", "coverage_request" : "fraud" }"#)]
pub frm_metadata: Option<pii::SecretSerdeValue>,
}

#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
pub struct PaymentChargeResponse {
/// Identifier for charge created for the payment
pub charge_id: Option<String>,

/// Type of charge (connector specific)
#[schema(value_type = PaymentChargeType, example = "direct")]
pub charge_type: api_enums::PaymentChargeType,

/// Platform fees collected on the payment
pub application_fees: i64,

/// Identifier for the reseller's account where the funds were transferred
pub transfer_account_id: String,
}

#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
pub struct ExternalAuthenticationDetailsResponse {
/// Authentication Type - Challenge / Frictionless
Expand Down
10 changes: 9 additions & 1 deletion crates/api_models/src/refunds.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;

use common_utils::pii;
pub use common_utils::types::ChargeRefunds;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;
Expand Down Expand Up @@ -53,6 +54,10 @@ pub struct RefundRequest {
/// Merchant connector details used to make payments.
#[schema(value_type = Option<MerchantConnectorDetailsWrap>)]
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,

/// Charge specific fields for controlling the revert of funds from either platform or connected account
#[schema(value_type = Option<ChargeRefunds>)]
pub charges: Option<ChargeRefunds>,
}

#[derive(Default, Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -137,6 +142,9 @@ pub struct RefundResponse {
pub profile_id: Option<String>,
/// The merchant_connector_id of the processor through which this payment went through
pub merchant_connector_id: Option<String>,
/// Charge specific fields for controlling the revert of funds from either platform or connected account
#[schema(value_type = Option<ChargeRefunds>)]
pub charges: Option<ChargeRefunds>,
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
Expand Down Expand Up @@ -168,7 +176,7 @@ pub struct RefundListRequest {
pub refund_status: Option<Vec<enums::RefundStatus>>,
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, ToSchema)]
pub struct RefundListResponse {
/// The number of refunds included in the list
pub count: usize,
Expand Down
2 changes: 1 addition & 1 deletion crates/common_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ thiserror = "1.0.58"
time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] }
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"], optional = true }
semver = { version = "1.0.22", features = ["serde"] }
uuid = { version = "1.8.0", features = ["v7"] }
utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] }
uuid = { version = "1.8.0", features = ["v7"] }

# First party crates
common_enums = { version = "0.1.0", path = "../common_enums" }
Expand Down
50 changes: 46 additions & 4 deletions crates/common_utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ use diesel::{
};
use error_stack::{report, ResultExt};
use semver::Version;
use serde::{de::Visitor, Deserialize, Deserializer};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use utoipa::ToSchema;

use crate::{
consts,
errors::{CustomResult, ParsingError, PercentageError},
};
/// Represents Percentage Value between 0 and 100 both inclusive
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize)]
#[derive(Clone, Default, Debug, PartialEq, Serialize)]
pub struct Percentage<const PRECISION: u8> {
// this value will range from 0 to 100, decimal length defined by precision macro
/// Percentage value ranging between 0 and 100
Expand Down Expand Up @@ -160,7 +160,7 @@ impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage<PRECISION> {
}

/// represents surcharge type and value
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case", tag = "type", content = "value")]
pub enum Surcharge {
/// Fixed Surcharge value
Expand All @@ -172,7 +172,7 @@ pub enum Surcharge {
/// This struct lets us represent a semantic version type
#[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, Ord, PartialOrd)]
#[diesel(sql_type = Jsonb)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Serialize, serde::Deserialize)]
pub struct SemanticVersion(#[serde(with = "Version")] Version);

impl SemanticVersion {
Expand Down Expand Up @@ -226,6 +226,48 @@ where
}
}

#[derive(
Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema,
)]
#[diesel(sql_type = Jsonb)]
/// Charge object for refunds
pub struct ChargeRefunds {
/// Identifier for charge created for the payment
pub charge_id: String,

/// Toggle for reverting the application fee that was collected for the payment.
/// If set to false, the funds are pulled from the destination account.
pub revert_platform_fee: Option<bool>,

/// Toggle for reverting the transfer that was made during the charge.
/// If set to false, the funds are pulled from the main platform's account.
pub revert_transfer: Option<bool>,
}

impl<DB: Backend> FromSql<Jsonb, DB> for ChargeRefunds
where
serde_json::Value: FromSql<Jsonb, DB>,
{
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as FromSql<Jsonb, DB>>::from_sql(bytes)?;
Ok(serde_json::from_value(value)?)
}
}

impl ToSql<Jsonb, diesel::pg::Pg> for ChargeRefunds
where
serde_json::Value: ToSql<Jsonb, diesel::pg::Pg>,
{
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> diesel::serialize::Result {
let value = serde_json::to_value(self)?;

// the function `reborrow` only works in case of `Pg` backend. But, in case of other backends
// please refer to the diesel migration blog:
// https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations
<serde_json::Value as ToSql<Jsonb, diesel::pg::Pg>>::to_sql(&value, &mut out.reborrow())
}
}

/// This Unit struct represents MinorUnit in which core amount works
#[derive(
Default,
Expand Down
11 changes: 11 additions & 0 deletions crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub struct PaymentAttempt {
pub mandate_data: Option<storage_enums::MandateDetails>,
pub fingerprint_id: Option<String>,
pub payment_method_billing_address_id: Option<String>,
pub charge_id: Option<String>,
pub client_source: Option<String>,
pub client_version: Option<String>,
}
Expand Down Expand Up @@ -152,6 +153,7 @@ pub struct PaymentAttemptNew {
pub mandate_data: Option<storage_enums::MandateDetails>,
pub fingerprint_id: Option<String>,
pub payment_method_billing_address_id: Option<String>,
pub charge_id: Option<String>,
pub client_source: Option<String>,
pub client_version: Option<String>,
}
Expand Down Expand Up @@ -281,6 +283,7 @@ pub enum PaymentAttemptUpdate {
unified_code: Option<Option<String>>,
unified_message: Option<Option<String>>,
payment_method_data: Option<serde_json::Value>,
charge_id: Option<String>,
},
UnresolvedResponseUpdate {
status: storage_enums::AttemptStatus,
Expand Down Expand Up @@ -334,6 +337,7 @@ pub enum PaymentAttemptUpdate {
encoded_data: Option<String>,
connector_transaction_id: Option<String>,
connector: Option<String>,
charge_id: Option<String>,
updated_by: String,
},
IncrementalAuthorizationAmountUpdate {
Expand Down Expand Up @@ -394,6 +398,7 @@ pub struct PaymentAttemptUpdateInternal {
authentication_id: Option<String>,
fingerprint_id: Option<String>,
payment_method_billing_address_id: Option<String>,
charge_id: Option<String>,
client_source: Option<String>,
client_version: Option<String>,
}
Expand Down Expand Up @@ -461,6 +466,7 @@ impl PaymentAttemptUpdate {
authentication_id,
payment_method_billing_address_id,
fingerprint_id,
charge_id,
client_source,
client_version,
} = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source);
Expand Down Expand Up @@ -511,6 +517,7 @@ impl PaymentAttemptUpdate {
payment_method_billing_address_id: payment_method_billing_address_id
.or(source.payment_method_billing_address_id),
fingerprint_id: fingerprint_id.or(source.fingerprint_id),
charge_id: charge_id.or(source.charge_id),
client_source: client_source.or(source.client_source),
client_version: client_version.or(source.client_version),
..source
Expand Down Expand Up @@ -698,6 +705,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
unified_code,
unified_message,
payment_method_data,
charge_id,
} => Self {
status: Some(status),
connector: connector.map(Some),
Expand All @@ -719,6 +727,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
unified_code,
unified_message,
payment_method_data,
charge_id,
..Default::default()
},
PaymentAttemptUpdate::ErrorUpdate {
Expand Down Expand Up @@ -841,12 +850,14 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
connector_transaction_id,
connector,
updated_by,
charge_id,
} => Self {
authentication_data,
encoded_data,
connector_transaction_id,
connector: connector.map(Some),
updated_by,
charge_id,
..Default::default()
},
PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate {
Expand Down
5 changes: 5 additions & 0 deletions crates/diesel_models/src/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub struct PaymentIntent {
pub session_expiry: Option<PrimitiveDateTime>,
pub fingerprint_id: Option<String>,
pub request_external_three_ds_authentication: Option<bool>,
pub charges: Option<pii::SecretSerdeValue>,
pub frm_metadata: Option<pii::SecretSerdeValue>,
}

Expand Down Expand Up @@ -112,6 +113,7 @@ pub struct PaymentIntentNew {
pub session_expiry: Option<PrimitiveDateTime>,
pub fingerprint_id: Option<String>,
pub request_external_three_ds_authentication: Option<bool>,
pub charges: Option<pii::SecretSerdeValue>,
pub frm_metadata: Option<pii::SecretSerdeValue>,
}

Expand Down Expand Up @@ -242,6 +244,7 @@ pub struct PaymentIntentUpdateInternal {
pub session_expiry: Option<PrimitiveDateTime>,
pub fingerprint_id: Option<String>,
pub request_external_three_ds_authentication: Option<bool>,
pub charges: Option<pii::SecretSerdeValue>,
pub frm_metadata: Option<pii::SecretSerdeValue>,
}

Expand Down Expand Up @@ -278,6 +281,7 @@ impl PaymentIntentUpdate {
session_expiry,
fingerprint_id,
request_external_three_ds_authentication,
charges,
frm_metadata,
} = self.into();
PaymentIntent {
Expand Down Expand Up @@ -316,6 +320,7 @@ impl PaymentIntentUpdate {
session_expiry: session_expiry.or(source.session_expiry),
request_external_three_ds_authentication: request_external_three_ds_authentication
.or(source.request_external_three_ds_authentication),
charges: charges.or(source.charges),

frm_metadata: frm_metadata.or(source.frm_metadata),
..source
Expand Down
Loading

0 comments on commit 55ccce6

Please sign in to comment.