Skip to content

Commit

Permalink
Merge branch 'main' into make-constraint-graphs-generic
Browse files Browse the repository at this point in the history
  • Loading branch information
vspecky committed Dec 14, 2023
2 parents d9746e3 + a8fdfc4 commit 393c34f
Show file tree
Hide file tree
Showing 65 changed files with 1,265 additions and 237 deletions.
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ All notable changes to HyperSwitch will be documented here.

- - -

## 1.100.0 (2023-12-14)

### Features

- **connector:**
- [RISKIFIED] Add support for riskified frm connector ([#2533](https://github.com/juspay/hyperswitch/pull/2533)) ([`151a30f`](https://github.com/juspay/hyperswitch/commit/151a30f4eed10924cd93bf7f4f66976af0ab8314))
- [HELCIM] Add connector_request_reference_id in invoice_number ([#3087](https://github.com/juspay/hyperswitch/pull/3087)) ([`3cc9642`](https://github.com/juspay/hyperswitch/commit/3cc9642f3ac4c07fb675e9ff4032832819d877a1))
- **core:** Enable surcharge support for all connectors ([#3109](https://github.com/juspay/hyperswitch/pull/3109)) ([`57e1ae9`](https://github.com/juspay/hyperswitch/commit/57e1ae9dea6ff70fb1bca47c479c35026c167bad))
- **events:** Add type info to outgoing requests & maintain structural & PII type info ([#2956](https://github.com/juspay/hyperswitch/pull/2956)) ([`6e82b0b`](https://github.com/juspay/hyperswitch/commit/6e82b0bd746b405281f79b86a3cd92b550a33f68))
- **external_services:** Adds encrypt function for KMS ([#3111](https://github.com/juspay/hyperswitch/pull/3111)) ([`bca7cdb`](https://github.com/juspay/hyperswitch/commit/bca7cdb4c14b5fbb40d8cbf59fd1756ad27ac674))

### Bug Fixes

- **api_locking:** Fix the unit interpretation for `LockSettings` expiry ([#3121](https://github.com/juspay/hyperswitch/pull/3121)) ([`3f4167d`](https://github.com/juspay/hyperswitch/commit/3f4167dbd477c793e1a4cc572da0c12d66f2b649))
- **connector:** [trustpay] make paymentId optional field ([#3101](https://github.com/juspay/hyperswitch/pull/3101)) ([`62a7c30`](https://github.com/juspay/hyperswitch/commit/62a7c3053c5e276091f5bd54a5679caef58a4ace))
- **docker-compose:** Remove label list from docker compose yml ([#3118](https://github.com/juspay/hyperswitch/pull/3118)) ([`e1e23fd`](https://github.com/juspay/hyperswitch/commit/e1e23fd987cae96e56311d1cfdcb225d9327860c))
- Validate refund amount with amount_captured instead of amount ([#3120](https://github.com/juspay/hyperswitch/pull/3120)) ([`be13d15`](https://github.com/juspay/hyperswitch/commit/be13d15d3c0214c863e131cf1dbe184d5baec5d7))

### Refactors

- **connector:** [Wise] Error Message For Connector Implementation ([#2952](https://github.com/juspay/hyperswitch/pull/2952)) ([`1add2c0`](https://github.com/juspay/hyperswitch/commit/1add2c059f4fb5653f33e2f3ce454793caf2d595))
- **payments:** Add support for receiving card_holder_name field as an empty string ([#3127](https://github.com/juspay/hyperswitch/pull/3127)) ([`4d19d8b`](https://github.com/juspay/hyperswitch/commit/4d19d8b1d18f49f02e951c5025d35cf5d62cec1b))

### Testing

- **postman:** Update postman collection files ([`a5618cd`](https://github.com/juspay/hyperswitch/commit/a5618cd5d6eb5b007f7927f05e777e875195a678))

**Full Changelog:** [`v1.99.0...v1.100.0`](https://github.com/juspay/hyperswitch/compare/v1.99.0...v1.100.0)

- - -


## 1.99.0 (2023-12-12)

### Features
Expand Down
4 changes: 1 addition & 3 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,7 @@ impl PaymentsRequest {
pub fn get_total_capturable_amount(&self) -> Option<i64> {
let surcharge_amount = self
.surcharge_details
.map(|surcharge_details| {
surcharge_details.surcharge_amount + surcharge_details.tax_amount.unwrap_or(0)
})
.map(|surcharge_details| surcharge_details.get_total_surcharge_amount())
.unwrap_or(0);
self.amount
.map(|amount| i64::from(amount) + surcharge_amount)
Expand Down
10 changes: 10 additions & 0 deletions crates/data_models/src/payments/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ pub struct PaymentAttempt {
pub unified_message: Option<String>,
}

impl PaymentAttempt {
pub fn get_total_amount(&self) -> i64 {
self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0)
}
pub fn get_total_surcharge_amount(&self) -> Option<i64> {
self.surcharge_amount
.map(|surcharge_amount| surcharge_amount + self.tax_amount.unwrap_or(0))
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PaymentListFilters {
pub connector: Vec<String>,
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/compatibility/stripe/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub enum StripeErrorCode {
expected_format: String,
},

#[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_06", message = "Refund amount exceeds the payment amount.")]
#[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_06", message = "The refund amount exceeds the amount captured.")]
RefundAmountExceedsPaymentAmount { param: String },

#[error(error_type = StripeErrorType::ApiError, code = "payment_intent_authentication_failure", message = "Payment failed while processing with connector. Retry payment.")]
Expand Down
9 changes: 1 addition & 8 deletions crates/router/src/connector/paypal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,6 @@ impl ConnectorValidation for Paypal {
),
}
}

fn validate_if_surcharge_implemented(&self) -> CustomResult<(), errors::ConnectorError> {
Ok(())
}
}

impl
Expand Down Expand Up @@ -423,10 +419,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
let connector_router_data = paypal::PaypalRouterData::try_from((
&self.get_currency_unit(),
req.request.currency,
req.request
.surcharge_details
.as_ref()
.map_or(req.request.amount, |surcharge| surcharge.final_amount),
req.request.amount,
req,
))?;
let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?;
Expand Down
20 changes: 3 additions & 17 deletions crates/router/src/connector/trustpay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,7 @@ impl ConnectorCommon for Trustpay {
}
}

impl ConnectorValidation for Trustpay {
fn validate_if_surcharge_implemented(&self) -> CustomResult<(), errors::ConnectorError> {
Ok(())
}
}
impl ConnectorValidation for Trustpay {}

impl api::Payment for Trustpay {}

Expand Down Expand Up @@ -432,12 +428,7 @@ impl
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let currency = req.request.get_currency()?;
let amount = req
.request
.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.final_amount)
.unwrap_or(req.request.get_amount()?);
let amount = req.request.get_amount()?;
let connector_router_data = trustpay::TrustpayRouterData::try_from((
&self.get_currency_unit(),
currency,
Expand Down Expand Up @@ -544,12 +535,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> {
let amount = req
.request
.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.final_amount)
.unwrap_or(req.request.amount);
let amount = req.request.amount;
let connector_router_data = trustpay::TrustpayRouterData::try_from((
&self.get_currency_unit(),
req.request.currency,
Expand Down
29 changes: 27 additions & 2 deletions crates/router/src/connector/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use crate::{
},
pii::PeekInterface,
types::{
self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom,
ApplePayPredecryptData, BrowserInformation, PaymentsCancelData, ResponseId,
self, api, transformers::ForeignTryFrom, ApplePayPredecryptData, BrowserInformation,
PaymentsCancelData, ResponseId,
},
utils::{OptionExt, ValueExt},
};
Expand Down Expand Up @@ -367,6 +367,10 @@ pub trait PaymentsAuthorizeRequestData {
fn get_connector_mandate_id(&self) -> Result<String, Error>;
fn get_complete_authorize_url(&self) -> Result<String, Error>;
fn get_ip_address_as_optional(&self) -> Option<Secret<String, IpAddress>>;
fn get_original_amount(&self) -> i64;
fn get_surcharge_amount(&self) -> Option<i64>;
fn get_tax_on_surcharge_amount(&self) -> Option<i64>;
fn get_total_surcharge_amount(&self) -> Option<i64>;
}

pub trait PaymentMethodTokenizationRequestData {
Expand Down Expand Up @@ -473,6 +477,27 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
.map(|ip| Secret::new(ip.to_string()))
})
}
fn get_original_amount(&self) -> i64 {
self.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.original_amount)
.unwrap_or(self.amount)
}
fn get_surcharge_amount(&self) -> Option<i64> {
self.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.surcharge_amount)
}
fn get_tax_on_surcharge_amount(&self) -> Option<i64> {
self.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.tax_on_surcharge_amount)
}
fn get_total_surcharge_amount(&self) -> Option<i64> {
self.surcharge_details
.as_ref()
.map(|surcharge_details| surcharge_details.get_total_surcharge_amount())
}
}

pub trait ConnectorCustomerData {
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/core/errors/api_error_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub enum ApiErrorResponse {
CustomerRedacted,
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")]
MaximumRefundCount,
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "Refund amount exceeds the payment amount")]
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")]
RefundAmountExceedsPaymentAmount,
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")]
PaymentUnexpectedState {
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/core/errors/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
}
Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)),
Self::RefundAmountExceedsPaymentAmount => {
AER::BadRequest(ApiError::new("IR", 13, "Refund amount exceeds the payment amount", None))
AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None))
}
Self::PaymentUnexpectedState {
current_flow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ fn get_surcharge_details_from_surcharge_output(
.transpose()?
.unwrap_or(0);
Ok(types::SurchargeDetails {
original_amount: payment_attempt.amount,
surcharge: match surcharge_details.surcharge {
surcharge_decision_configs::SurchargeOutput::Fixed { amount } => {
common_utils_types::Surcharge::Fixed(amount)
Expand Down
12 changes: 8 additions & 4 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,6 @@ where
);

if should_continue_transaction {
operation
.to_domain()?
.populate_payment_data(state, &mut payment_data, &merchant_account)
.await?;
payment_data = match connector_details {
api::ConnectorCallType::PreDetermined(connector) => {
let schedule_time = if should_add_task_to_process_tracker {
Expand Down Expand Up @@ -484,6 +480,13 @@ where
.surcharge_applicable
.unwrap_or(false)
{
if let Some(surcharge_details) = payment_data.payment_attempt.get_surcharge_details() {
// if retry payment, surcharge would have been populated from the previous attempt. Use the same surcharge
let surcharge_details =
types::SurchargeDetails::from((&surcharge_details, &payment_data.payment_attempt));
payment_data.surcharge_details = Some(surcharge_details);
return Ok(());
}
let raw_card_key = payment_data
.payment_method_data
.as_ref()
Expand Down Expand Up @@ -562,6 +565,7 @@ where
payment_data.payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount;
Ok(Some(api::SessionSurchargeDetails::PreDetermined(
types::SurchargeDetails {
original_amount: payment_data.payment_attempt.amount,
surcharge: Surcharge::Fixed(surcharge_amount),
tax_on_surcharge: None,
surcharge_amount,
Expand Down
6 changes: 0 additions & 6 deletions crates/router/src/core/payments/flows/authorize_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,6 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
.connector
.validate_capture_method(self.request.capture_method)
.to_payment_failed_response()?;
if self.request.surcharge_details.is_some() {
connector
.connector
.validate_if_surcharge_implemented()
.to_payment_failed_response()?;
}

if self.should_proceed_with_authorize() {
self.decide_authentication_type();
Expand Down
58 changes: 13 additions & 45 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -946,42 +946,6 @@ pub fn verify_mandate_details(
)
}

// This function validates card_holder_name field to be either null or a non-empty string
pub fn validate_card_holder_name(
payment_method_data: Option<api::PaymentMethodData>,
) -> CustomResult<(), errors::ApiErrorResponse> {
if let Some(pmd) = payment_method_data {
match pmd {
// This validation would occur during payments create
api::PaymentMethodData::Card(card) => {
if let Some(name) = &card.card_holder_name {
if name.clone().expose().is_empty() {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "card_holder_name cannot be empty".to_string(),
})
.into_report();
}
}
}

// This validation would occur during payments confirm
api::PaymentMethodData::CardToken(card) => {
if let Some(name) = card.card_holder_name {
if name.expose().is_empty() {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "card_holder_name cannot be empty".to_string(),
})
.into_report();
}
}
}
_ => (),
}
}

Ok(())
}

#[instrument(skip_all)]
pub fn payment_attempt_status_fsm(
payment_method_data: &Option<api::PaymentMethodData>,
Expand Down Expand Up @@ -1441,13 +1405,15 @@ pub async fn retrieve_payment_method_with_temporary_token(
let mut is_card_updated = false;

// The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked
// from payment_method.card_token object
// from payment_method_data.card_token object
let name_on_card = if let Some(name) = card.card_holder_name.clone() {
if name.expose().is_empty() {
card_token_data.and_then(|token_data| {
is_card_updated = true;
token_data.card_holder_name.clone()
})
if name.clone().expose().is_empty() {
card_token_data
.and_then(|token_data| {
is_card_updated = true;
token_data.card_holder_name.clone()
})
.or(Some(name))
} else {
card.card_holder_name.clone()
}
Expand Down Expand Up @@ -1528,10 +1494,12 @@ pub async fn retrieve_card_with_permanent_token(
.attach_printable("failed to fetch card information from the permanent locker")?;

// The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked
// from payment_method.card_token object
// from payment_method_data.card_token object
let name_on_card = if let Some(name) = card.name_on_card.clone() {
if name.expose().is_empty() {
card_token_data.and_then(|token_data| token_data.card_holder_name.clone())
if name.clone().expose().is_empty() {
card_token_data
.and_then(|token_data| token_data.card_holder_name.clone())
.or(Some(name))
} else {
card.name_on_card
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
payment_method_type.or(payment_attempt.payment_method_type);
payment_attempt.payment_experience = request.payment_experience;
currency = payment_attempt.currency.get_required_value("currency")?;
amount = payment_attempt.amount.into();
amount = payment_attempt.get_total_amount().into();

helpers::validate_customer_id_mandatory_cases(
request.setup_future_usage.is_some(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
.await?;

let currency = payment_attempt.currency.get_required_value("currency")?;
let amount = payment_attempt.amount.into();
let amount = payment_attempt.get_total_amount().into();

payment_attempt.cancellation_reason = request.cancellation_reason.clone();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>

currency = payment_attempt.currency.get_required_value("currency")?;

amount = payment_attempt.amount.into();
amount = payment_attempt.get_total_amount().into();

let shipping_address = helpers::create_or_find_address_for_payment_by_request(
db,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
.payment_experience
.or(payment_attempt.payment_experience);
currency = payment_attempt.currency.get_required_value("currency")?;
amount = payment_attempt.amount.into();
amount = payment_attempt.get_total_amount().into();

helpers::validate_customer_id_mandatory_cases(
request.setup_future_usage.is_some(),
Expand Down
Loading

0 comments on commit 393c34f

Please sign in to comment.