mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 21:37:41 +08:00
feat(charges): integrated PaymentSync for stripe connect (#4771)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -761,6 +761,13 @@ impl
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
req.request.charges.as_ref().map(|charges| {
|
||||
transformers::transform_headers_for_connect_platform(
|
||||
charges.charge_type.clone(),
|
||||
charges.transfer_account_id.clone(),
|
||||
&mut header,
|
||||
)
|
||||
});
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
@ -1618,6 +1625,13 @@ impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::Refun
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut api_key);
|
||||
req.request.charges.as_ref().map(|charges| {
|
||||
transformers::transform_headers_for_connect_platform(
|
||||
charges.charge_type.clone(),
|
||||
charges.transfer_account_id.clone(),
|
||||
&mut header,
|
||||
)
|
||||
});
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ use common_utils::{
|
||||
use diesel_models::enums as storage_enums;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::mandates::AcceptanceType;
|
||||
use masking::{ExposeInterface, ExposeOptionInterface, PeekInterface, Secret};
|
||||
use masking::{ExposeInterface, ExposeOptionInterface, Mask, PeekInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use time::PrimitiveDateTime;
|
||||
@ -27,7 +27,7 @@ use crate::{
|
||||
},
|
||||
consts,
|
||||
core::errors,
|
||||
services,
|
||||
headers, services,
|
||||
types::{
|
||||
self, api, domain,
|
||||
storage::enums,
|
||||
@ -4025,6 +4025,21 @@ impl ForeignTryFrom<(&Option<ErrorDetails>, u16, String)> for types::PaymentsRes
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn transform_headers_for_connect_platform(
|
||||
charge_type: api::enums::PaymentChargeType,
|
||||
transfer_account_id: String,
|
||||
header: &mut Vec<(String, services::request::Maskable<String>)>,
|
||||
) {
|
||||
if let api::enums::PaymentChargeType::Stripe(api::enums::StripeChargeType::Direct) = charge_type
|
||||
{
|
||||
let mut customer_account_header = vec![(
|
||||
headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(),
|
||||
transfer_account_id.into_masked(),
|
||||
)];
|
||||
header.append(&mut customer_account_header);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_validate_shipping_address_against_payment_method {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
@ -5,7 +5,7 @@ use api_models::customers::CustomerRequestWithEmail;
|
||||
use api_models::{
|
||||
mandates::RecurringDetails,
|
||||
payments::{
|
||||
additional_info as payment_additional_types, AddressDetailsWithPhone,
|
||||
additional_info as payment_additional_types, AddressDetailsWithPhone, PaymentChargeRequest,
|
||||
RequestSurchargeDetails,
|
||||
},
|
||||
};
|
||||
@ -5741,3 +5741,29 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_platform_fees_for_marketplace(
|
||||
amount: api::Amount,
|
||||
charges: &PaymentChargeRequest,
|
||||
) -> Result<(), errors::ApiErrorResponse> {
|
||||
match amount {
|
||||
api::Amount::Zero => {
|
||||
if charges.fees.get_amount_as_i64() != 0 {
|
||||
Err(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "charges.fees",
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
api::Amount::Value(amount) => {
|
||||
if charges.fees.get_amount_as_i64() > amount.into() {
|
||||
Err(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "charges.fees",
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -952,6 +952,11 @@ impl<F: Send + Clone> ValidateRequest<F, api::PaymentsRequest, PaymentData<F>> f
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(charges) = &request.charges {
|
||||
let amount = request.amount.get_required_value("amount")?;
|
||||
helpers::validate_platform_fees_for_marketplace(amount, charges)?;
|
||||
};
|
||||
|
||||
let _request_straight_through: Option<api::routing::StraightThroughAlgorithm> = request
|
||||
.routing
|
||||
.clone()
|
||||
|
||||
@ -1792,6 +1792,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthoriz
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))]
|
||||
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData {
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
|
||||
@ -1823,6 +1824,57 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData
|
||||
},
|
||||
payment_method_type: payment_data.payment_attempt.payment_method_type,
|
||||
currency: payment_data.currency,
|
||||
charges: payment_data
|
||||
.payment_intent
|
||||
.charges
|
||||
.as_ref()
|
||||
.map(|charges| {
|
||||
charges
|
||||
.peek()
|
||||
.clone()
|
||||
.parse_value("PaymentCharges")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse charges in to PaymentCharges")
|
||||
})
|
||||
.transpose()?,
|
||||
payment_experience: payment_data.payment_attempt.payment_experience,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "payment_v2"))]
|
||||
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData {
|
||||
type Error = error_stack::Report<errors::ApiErrorResponse>;
|
||||
|
||||
fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
|
||||
let payment_data = additional_data.payment_data;
|
||||
let amount = payment_data
|
||||
.surcharge_details
|
||||
.as_ref()
|
||||
.map(|surcharge_details| surcharge_details.final_amount)
|
||||
.unwrap_or(payment_data.amount.into());
|
||||
Ok(Self {
|
||||
amount,
|
||||
integrity_object: None,
|
||||
mandate_id: payment_data.mandate_id.clone(),
|
||||
connector_transaction_id: match payment_data.payment_attempt.connector_transaction_id {
|
||||
Some(connector_txn_id) => {
|
||||
types::ResponseId::ConnectorTransactionId(connector_txn_id)
|
||||
}
|
||||
None => types::ResponseId::NoResponseId,
|
||||
},
|
||||
encoded_data: payment_data.payment_attempt.encoded_data,
|
||||
capture_method: payment_data.payment_attempt.capture_method,
|
||||
connector_meta: payment_data.payment_attempt.connector_metadata,
|
||||
sync_type: match payment_data.multiple_capture_data {
|
||||
Some(multiple_capture_data) => types::SyncRequestType::MultipleCaptureSync(
|
||||
multiple_capture_data.get_pending_connector_capture_ids(),
|
||||
),
|
||||
None => types::SyncRequestType::SinglePaymentSync,
|
||||
},
|
||||
payment_method_type: payment_data.payment_attempt.payment_method_type,
|
||||
currency: payment_data.currency,
|
||||
charges: None,
|
||||
payment_experience: payment_data.payment_attempt.payment_experience,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod transformers;
|
||||
pub mod validator;
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
@ -34,7 +35,7 @@ use crate::{
|
||||
api::{self, refunds},
|
||||
domain,
|
||||
storage::{self, enums},
|
||||
transformers::{ForeignFrom, ForeignInto},
|
||||
transformers::{ForeignFrom, ForeignInto, ForeignTryFrom},
|
||||
ChargeRefunds,
|
||||
},
|
||||
utils::{self, OptionExt},
|
||||
@ -444,6 +445,15 @@ pub async fn refund_retrieve_core(
|
||||
.await
|
||||
.transpose()?;
|
||||
|
||||
let charges_req = payment_intent
|
||||
.charges
|
||||
.clone()
|
||||
.zip(refund.charges.clone())
|
||||
.map(|(charges, refund_charges)| {
|
||||
ForeignTryFrom::foreign_try_from((refund_charges, charges))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) {
|
||||
sync_refund_with_gateway(
|
||||
&state,
|
||||
@ -453,6 +463,7 @@ pub async fn refund_retrieve_core(
|
||||
&payment_intent,
|
||||
&refund,
|
||||
creds_identifier,
|
||||
charges_req,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@ -479,6 +490,7 @@ fn should_call_refund(refund: &diesel_models::refund::Refund, force_sync: bool)
|
||||
predicate1 && predicate2
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn sync_refund_with_gateway(
|
||||
state: &SessionState,
|
||||
@ -488,6 +500,7 @@ pub async fn sync_refund_with_gateway(
|
||||
payment_intent: &storage::PaymentIntent,
|
||||
refund: &storage::Refund,
|
||||
creds_identifier: Option<String>,
|
||||
charges: Option<ChargeRefunds>,
|
||||
) -> RouterResult<storage::Refund> {
|
||||
let connector_id = refund.connector.to_string();
|
||||
let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
|
||||
@ -513,7 +526,7 @@ pub async fn sync_refund_with_gateway(
|
||||
payment_attempt,
|
||||
refund,
|
||||
creds_identifier.clone(),
|
||||
None,
|
||||
charges,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
32
crates/router/src/core/refunds/transformers.rs
Normal file
32
crates/router/src/core/refunds/transformers.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use common_utils::{ext_traits::ValueExt, pii, types::ChargeRefunds};
|
||||
use error_stack::{Report, ResultExt};
|
||||
use hyperswitch_domain_models::router_request_types;
|
||||
use masking::PeekInterface;
|
||||
|
||||
use super::validator;
|
||||
use crate::{core::errors, types::transformers::ForeignTryFrom};
|
||||
|
||||
impl ForeignTryFrom<(ChargeRefunds, pii::SecretSerdeValue)>
|
||||
for router_request_types::ChargeRefunds
|
||||
{
|
||||
type Error = Report<errors::ApiErrorResponse>;
|
||||
fn foreign_try_from(item: (ChargeRefunds, pii::SecretSerdeValue)) -> Result<Self, Self::Error> {
|
||||
let (refund_charges, charges) = item;
|
||||
let payment_charges: router_request_types::PaymentCharges = charges
|
||||
.peek()
|
||||
.clone()
|
||||
.parse_value("PaymentCharges")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse charges into PaymentCharges")?;
|
||||
|
||||
Ok(Self {
|
||||
charge_id: refund_charges.charge_id.clone(),
|
||||
charge_type: payment_charges.charge_type.clone(),
|
||||
transfer_account_id: payment_charges.transfer_account_id,
|
||||
options: validator::validate_charge_refund(
|
||||
&refund_charges,
|
||||
&payment_charges.charge_type,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user