Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router): add proxy-confirm-intent api for payments in v2 flow #7215

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c088d28
add revenue_rcovery_metadata to payment intent in diesel and api model
Feb 3, 2025
9ddd2d7
chore: run formatter
hyperswitch-bot[bot] Feb 3, 2025
76ccac4
remove unncessary imports
Feb 4, 2025
f3338d8
refactor structs of Feature Metadata
Feb 4, 2025
c846fab
refactor(router) : add revenue_recovery_metadata to feature_metadata …
Feb 4, 2025
a0759eb
chore: run formatter
hyperswitch-bot[bot] Feb 4, 2025
db0ebaf
add v2 version of proxy_payments function
AdityaKumaar21 Jan 24, 2025
d34f32f
correct the construct_router_data and update_trackers
AdityaKumaar21 Jan 27, 2025
c9ff63e
add proxy-confirm-intent api
AdityaKumaar21 Feb 4, 2025
684b5e7
change paymentAttemptNew and paymentConfirmData struct
AdityaKumaar21 Feb 6, 2025
f3e12bc
remove all the warnings
AdityaKumaar21 Feb 7, 2025
42d67e2
up to date my branch with main
AdityaKumaar21 Feb 7, 2025
e0eedec
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 7, 2025
1ae403f
chore: run formatter
hyperswitch-bot[bot] Feb 7, 2025
81c3cb5
update PaymentAttempt structure to use connector_token_details and ca…
AdityaKumaar21 Feb 12, 2025
5aae46b
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 12, 2025
1345b0b
chore: run formatter
hyperswitch-bot[bot] Feb 12, 2025
fe61e7d
add feature_metadata to PaymentIntentUpdate and update handling in pr…
AdityaKumaar21 Feb 13, 2025
40b7b36
chore: run formatter
hyperswitch-bot[bot] Feb 13, 2025
db079f2
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 13, 2025
f53ecd5
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Feb 13, 2025
0bf7d6b
update PaymentAttempt to handle authentication type and adjust charge…
AdityaKumaar21 Feb 13, 2025
155fc9c
chore: run formatter
hyperswitch-bot[bot] Feb 13, 2025
1d20405
pass ci_checks
AdityaKumaar21 Feb 14, 2025
0eb0053
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 14, 2025
c3bba5f
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 16, 2025
f671019
refactor(payments): update revenue recovery metadata references and d…
AdityaKumaar21 Feb 16, 2025
dfbe3df
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 16, 2025
90f5cda
chore: run formatter
hyperswitch-bot[bot] Feb 16, 2025
ff0e36e
refactor(router): change default feature from v2 to v1
AdityaKumaar21 Feb 16, 2025
bcb3aa0
ci checks pass
AdityaKumaar21 Feb 17, 2025
34e675f
resolve comments and update the branch
AdityaKumaar21 Feb 24, 2025
093d2af
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 24, 2025
fd2958a
chore: run formatter
hyperswitch-bot[bot] Feb 24, 2025
5ca3d40
Merge branch 'main' into proxyAPIs
AdityaKumaar21 Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Corse-du-Sud = "Corse-du-Sud" # Is a state in France
Haute-Corse = "Haute-Corse" # Is a state in France
Fram = "Fram" # Is a state in Slovenia


[default.extend-words]
aci = "aci" # Name of a connector
afe = "afe" # Commit id
Expand Down
11 changes: 10 additions & 1 deletion crates/api_models/src/events/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
#[cfg(feature = "v2")]
use super::{
PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest,
PaymentsGetIntentRequest, PaymentsIntentResponse, PaymentsRequest,
PaymentsGetIntentRequest, PaymentsIntentResponse, PaymentsRequest, ProxyPaymentsIntentResponse,
};
#[cfg(all(
any(feature = "v2", feature = "v1"),
Expand Down Expand Up @@ -194,6 +194,15 @@ impl ApiEventMetric for PaymentsConfirmIntentResponse {
}
}

#[cfg(feature = "v2")]
impl ApiEventMetric for ProxyPaymentsIntentResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.id.clone(),
})
}
}

#[cfg(feature = "v2")]
impl ApiEventMetric for super::PaymentsRetrieveResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Expand Down
95 changes: 95 additions & 0 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use utoipa::ToSchema;
#[cfg(feature = "v1")]
use crate::ephemeral_key::EphemeralKeyCreateResponse;
#[cfg(feature = "v2")]
use crate::mandates::ProcessorPaymentToken;
#[cfg(feature = "v2")]
use crate::payment_methods;
use crate::{
admin::{self, MerchantConnectorInfo},
Expand Down Expand Up @@ -5099,6 +5101,32 @@ pub struct PaymentsConfirmIntentRequest {
pub browser_info: Option<common_utils::types::BrowserInformation>,
}

#[cfg(feature = "v2")]
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(deny_unknown_fields)]
pub struct ProxyPaymentsRequest {
/// The URL to which you want the user to be redirected after the completion of the payment operation
/// If this url is not passed, the url configured in the business profile will be used
#[schema(value_type = Option<String>, example = "https://hyperswitch.io")]
pub return_url: Option<common_utils::types::Url>,

pub amount: AmountDetails,

pub recurring_details: ProcessorPaymentToken,

pub shipping: Option<Address>,

/// Additional details required by 3DS 2.0
#[schema(value_type = Option<BrowserInformation>)]
pub browser_info: Option<common_utils::types::BrowserInformation>,

#[schema(example = "stripe")]
pub connector: String,

#[schema(value_type = String)]
pub merchant_connector_id: id_type::MerchantConnectorAccountId,
}

// This struct contains the union of fields in `PaymentsCreateIntentRequest` and
// `PaymentsConfirmIntentRequest`
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
Expand Down Expand Up @@ -5488,6 +5516,73 @@ pub struct PaymentsConfirmIntentResponse {
pub applied_authentication_type: api_enums::AuthenticationType,
}

#[cfg(feature = "v2")]
#[derive(Debug, serde::Serialize, ToSchema)]
pub struct ProxyPaymentsIntentResponse {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use payments response here instead of creating a new response?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applied_authentication_type this is a required field in confirmIntentResponse and is not relevant to proxyResponse

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes still, let's use the same response, if there are any changes make those fields optional

/// Unique identifier for the payment. This ensures idempotency for multiple payments
/// that have been done by a single merchant.
#[schema(
min_length = 32,
max_length = 64,
example = "12345_pay_01926c58bc6e77c09e809964e72af8c8",
value_type = String,
)]
pub id: id_type::GlobalPaymentId,

#[schema(value_type = IntentStatus, example = "success")]
pub status: api_enums::IntentStatus,

/// Amount related information for this payment and attempt
pub amount: PaymentAmountDetailsResponse,

/// The connector used for the payment
#[schema(example = "stripe")]
pub connector: String,

/// It's a token used for client side verification.
#[schema(value_type = String)]
pub client_secret: common_utils::types::ClientSecret,

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

/// The payment method information provided for making a payment
#[schema(value_type = Option<PaymentMethodDataResponseWithBilling>)]
#[serde(serialize_with = "serialize_payment_method_data_response")]
pub payment_method_data: Option<PaymentMethodDataResponseWithBilling>,

/// The payment method type for this payment attempt
#[schema(value_type = PaymentMethod, example = "wallet")]
pub payment_method_type: api_enums::PaymentMethod,

#[schema(value_type = PaymentMethodType, example = "apple_pay")]
pub payment_method_subtype: api_enums::PaymentMethodType,

/// Additional information required for redirection
pub next_action: Option<NextActionData>,

/// A unique identifier for a payment provided by the connector
#[schema(value_type = Option<String>, example = "993672945374576J")]
pub connector_transaction_id: Option<String>,

/// reference(Identifier) to the payment at connector side
#[schema(value_type = Option<String>, example = "993672945374576J")]
pub connector_reference_id: Option<String>,

/// Identifier of the connector ( merchant connector account ) which was chosen to make the payment
#[schema(value_type = String)]
pub merchant_connector_id: id_type::MerchantConnectorAccountId,

/// The browser information used for this payment
#[schema(value_type = Option<BrowserInformation>)]
pub browser_info: Option<common_utils::types::BrowserInformation>,

/// Error details for the payment if any
pub error: Option<ErrorDetails>,
}

/// Token information that can be used to initiate transactions by the merchant.
#[cfg(feature = "v2")]
#[derive(Debug, Serialize, ToSchema)]
Expand Down
2 changes: 1 addition & 1 deletion crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ pub struct PaymentAttemptNew {
pub capture_before: Option<PrimitiveDateTime>,
pub charges: Option<common_types::payments::ConnectorChargeResponseData>,
pub feature_metadata: Option<PaymentAttemptFeatureMetadata>,
pub connector: Option<String>,
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -3545,7 +3546,6 @@ pub struct PaymentAttemptFeatureMetadata {
pub struct PaymentAttemptRecoveryData {
pub attempt_triggered_by: common_enums::TriggeredBy,
}

#[cfg(feature = "v2")]
common_utils::impl_to_sql_from_sql_json!(PaymentAttemptFeatureMetadata);

Expand Down
9 changes: 6 additions & 3 deletions crates/diesel_models/src/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,14 @@ impl TaxDetails {
/// Get the tax amount
/// If default tax is present, return the default tax amount
/// If default tax is not present, return the tax amount based on the payment method if it matches the provided payment method type
pub fn get_tax_amount(&self, payment_method: PaymentMethodType) -> Option<MinorUnit> {
pub fn get_tax_amount(&self, payment_method: Option<PaymentMethodType>) -> Option<MinorUnit> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the field name to payment_method_type

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a subfield in PaymentMethodType with same name of payment_method_type and this subfield is also being used in this function

self.payment_method_type
.as_ref()
.filter(|payment_method_type_tax| payment_method_type_tax.pmt == payment_method)
.map(|payment_method_type_tax| payment_method_type_tax.order_tax_amount)
.zip(payment_method)
.filter(|(payment_method_type_tax, payment_method)| {
payment_method_type_tax.pmt == *payment_method
})
.map(|(payment_method_type_tax, _)| payment_method_type_tax.order_tax_amount)
.or_else(|| self.get_default_tax_amount())
}

Expand Down
6 changes: 3 additions & 3 deletions crates/diesel_models/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(feature = "v2")]
use common_enums::{enums::PaymentConnectorTransmission, PaymentMethod, PaymentMethodType};
use common_enums::enums::PaymentConnectorTransmission;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not import these types, qualify them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is of someone else's PR, I have quantify them in my pr as suggested by you

use common_utils::{hashing::HashedString, pii, types::MinorUnit};
use diesel::{
sql_types::{Json, Jsonb},
Expand Down Expand Up @@ -137,9 +137,9 @@ pub struct PaymentRevenueRecoveryMetadata {
/// Billing Connector Payment Details
pub billing_connector_payment_details: BillingConnectorPaymentDetails,
///Payment Method Type
pub payment_method_type: PaymentMethod,
pub payment_method_type: common_enums::enums::PaymentMethod,
/// PaymentMethod Subtype
pub payment_method_subtype: PaymentMethodType,
pub payment_method_subtype: common_enums::enums::PaymentMethodType,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
Expand Down
41 changes: 39 additions & 2 deletions crates/hyperswitch_domain_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ impl AmountDetails {
let order_tax_amount = match self.skip_external_tax_calculation {
common_enums::TaxCalculationOverride::Skip => {
self.tax_details.as_ref().and_then(|tax_details| {
tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype)
tax_details.get_tax_amount(Some(confirm_intent_request.payment_method_subtype))
})
}
common_enums::TaxCalculationOverride::Calculate => None,
Expand All @@ -243,6 +243,42 @@ impl AmountDetails {
})
}

pub fn proxy_create_attempt_amount_details(
&self,
_confirm_intent_request: &api_models::payments::ProxyPaymentsRequest,
) -> payment_attempt::AttemptAmountDetails {
let net_amount = self.calculate_net_amount();

let surcharge_amount = match self.skip_surcharge_calculation {
common_enums::SurchargeCalculationOverride::Skip => self.surcharge_amount,
common_enums::SurchargeCalculationOverride::Calculate => None,
};

let tax_on_surcharge = match self.skip_surcharge_calculation {
common_enums::SurchargeCalculationOverride::Skip => self.tax_on_surcharge,
common_enums::SurchargeCalculationOverride::Calculate => None,
};

let order_tax_amount = match self.skip_external_tax_calculation {
common_enums::TaxCalculationOverride::Skip => self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will always get the default tax amount here, and not payment method type tax

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bro since im passing payment method type from featuremetadata always, what should i do then

.tax_details
.as_ref()
.and_then(|tax_details| tax_details.get_tax_amount(None)),
common_enums::TaxCalculationOverride::Calculate => None,
};

payment_attempt::AttemptAmountDetails::from(payment_attempt::AttemptAmountDetailsSetter {
net_amount,
amount_to_capture: None,
surcharge_amount,
tax_on_surcharge,
// This will be updated when we receive response from the connector
amount_capturable: MinorUnit::zero(),
shipping_cost: self.shipping_cost,
order_tax_amount,
})
}

pub fn update_from_request(self, req: &api_models::payments::AmountDetailsUpdate) -> Self {
Self {
order_amount: req
Expand Down Expand Up @@ -585,7 +621,7 @@ where

// TODO: Check if this can be merged with existing payment data
#[cfg(feature = "v2")]
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct PaymentConfirmData<F>
where
F: Clone,
Expand All @@ -595,6 +631,7 @@ where
pub payment_attempt: PaymentAttempt,
pub payment_method_data: Option<payment_method_data::PaymentMethodData>,
pub payment_address: payment_address::PaymentAddress,
pub mandate_data: Option<api_models::payments::MandateIds>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not be using types from the api models here
cc: @Aprabhat19

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should i create this MandateIDs in hyperswitch_domain_model because it is not present there

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes please, create a struct in domain models for this.

}

#[cfg(feature = "v2")]
Expand Down
Loading
Loading